1 /* 2 * Copyright (C) 2018 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.statusbar.phone; 18 19 import android.annotation.NonNull; 20 import android.content.Context; 21 import android.os.Handler; 22 import android.telephony.SubscriptionInfo; 23 import android.util.ArraySet; 24 import android.util.Log; 25 26 import com.android.settingslib.mobile.TelephonyIcons; 27 import com.android.systemui.R; 28 import com.android.systemui.dagger.SysUISingleton; 29 import com.android.systemui.statusbar.connectivity.IconState; 30 import com.android.systemui.statusbar.connectivity.MobileDataIndicators; 31 import com.android.systemui.statusbar.connectivity.NetworkController; 32 import com.android.systemui.statusbar.connectivity.SignalCallback; 33 import com.android.systemui.statusbar.connectivity.WifiIndicators; 34 import com.android.systemui.statusbar.policy.SecurityController; 35 import com.android.systemui.tuner.TunerService; 36 import com.android.systemui.tuner.TunerService.Tunable; 37 import com.android.systemui.util.CarrierConfigTracker; 38 39 import java.util.ArrayList; 40 import java.util.List; 41 import java.util.Objects; 42 43 import javax.inject.Inject; 44 45 /** Controls the signal policies for icons shown in the statusbar. **/ 46 @SysUISingleton 47 public class StatusBarSignalPolicy implements SignalCallback, 48 SecurityController.SecurityControllerCallback, Tunable { 49 private static final String TAG = "StatusBarSignalPolicy"; 50 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 51 52 private final String mSlotAirplane; 53 private final String mSlotMobile; 54 private final String mSlotWifi; 55 private final String mSlotEthernet; 56 private final String mSlotVpn; 57 private final String mSlotNoCalling; 58 private final String mSlotCallStrength; 59 60 private final Context mContext; 61 private final StatusBarIconController mIconController; 62 private final NetworkController mNetworkController; 63 private final SecurityController mSecurityController; 64 private final Handler mHandler = Handler.getMain(); 65 private final CarrierConfigTracker mCarrierConfigTracker; 66 private final TunerService mTunerService; 67 68 private boolean mHideAirplane; 69 private boolean mHideMobile; 70 private boolean mHideWifi; 71 private boolean mHideEthernet; 72 private boolean mActivityEnabled; 73 74 // Track as little state as possible, and only for padding purposes 75 private boolean mIsAirplaneMode = false; 76 private boolean mIsWifiEnabled = false; 77 78 private ArrayList<MobileIconState> mMobileStates = new ArrayList<>(); 79 private ArrayList<CallIndicatorIconState> mCallIndicatorStates = new ArrayList<>(); 80 private WifiIconState mWifiIconState = new WifiIconState(); 81 private boolean mInitialized; 82 83 @Inject StatusBarSignalPolicy( Context context, StatusBarIconController iconController, CarrierConfigTracker carrierConfigTracker, NetworkController networkController, SecurityController securityController, TunerService tunerService )84 public StatusBarSignalPolicy( 85 Context context, 86 StatusBarIconController iconController, 87 CarrierConfigTracker carrierConfigTracker, 88 NetworkController networkController, 89 SecurityController securityController, 90 TunerService tunerService 91 ) { 92 mContext = context; 93 94 mIconController = iconController; 95 mCarrierConfigTracker = carrierConfigTracker; 96 mNetworkController = networkController; 97 mSecurityController = securityController; 98 mTunerService = tunerService; 99 100 mSlotAirplane = mContext.getString(com.android.internal.R.string.status_bar_airplane); 101 mSlotMobile = mContext.getString(com.android.internal.R.string.status_bar_mobile); 102 mSlotWifi = mContext.getString(com.android.internal.R.string.status_bar_wifi); 103 mSlotEthernet = mContext.getString(com.android.internal.R.string.status_bar_ethernet); 104 mSlotVpn = mContext.getString(com.android.internal.R.string.status_bar_vpn); 105 mSlotNoCalling = mContext.getString(com.android.internal.R.string.status_bar_no_calling); 106 mSlotCallStrength = 107 mContext.getString(com.android.internal.R.string.status_bar_call_strength); 108 mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity); 109 } 110 111 /** Call to initilaize and register this classw with the system. */ init()112 public void init() { 113 if (mInitialized) { 114 return; 115 } 116 mInitialized = true; 117 mTunerService.addTunable(this, StatusBarIconController.ICON_HIDE_LIST); 118 mNetworkController.addCallback(this); 119 mSecurityController.addCallback(this); 120 } 121 destroy()122 public void destroy() { 123 mTunerService.removeTunable(this); 124 mNetworkController.removeCallback(this); 125 mSecurityController.removeCallback(this); 126 } 127 updateVpn()128 private void updateVpn() { 129 boolean vpnVisible = mSecurityController.isVpnEnabled(); 130 int vpnIconId = currentVpnIconId(mSecurityController.isVpnBranded()); 131 132 mIconController.setIcon(mSlotVpn, vpnIconId, 133 mContext.getResources().getString(R.string.accessibility_vpn_on)); 134 mIconController.setIconVisibility(mSlotVpn, vpnVisible); 135 } 136 currentVpnIconId(boolean isBranded)137 private int currentVpnIconId(boolean isBranded) { 138 return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic; 139 } 140 141 /** 142 * From SecurityController 143 */ 144 @Override onStateChanged()145 public void onStateChanged() { 146 mHandler.post(this::updateVpn); 147 } 148 149 @Override onTuningChanged(String key, String newValue)150 public void onTuningChanged(String key, String newValue) { 151 if (!StatusBarIconController.ICON_HIDE_LIST.equals(key)) { 152 return; 153 } 154 ArraySet<String> hideList = StatusBarIconController.getIconHideList(mContext, newValue); 155 boolean hideAirplane = hideList.contains(mSlotAirplane); 156 boolean hideMobile = hideList.contains(mSlotMobile); 157 boolean hideWifi = hideList.contains(mSlotWifi); 158 boolean hideEthernet = hideList.contains(mSlotEthernet); 159 160 if (hideAirplane != mHideAirplane || hideMobile != mHideMobile 161 || hideEthernet != mHideEthernet || hideWifi != mHideWifi) { 162 mHideAirplane = hideAirplane; 163 mHideMobile = hideMobile; 164 mHideEthernet = hideEthernet; 165 mHideWifi = hideWifi; 166 // Re-register to get new callbacks. 167 mNetworkController.removeCallback(this); 168 mNetworkController.addCallback(this); 169 } 170 } 171 172 @Override setWifiIndicators(@onNull WifiIndicators indicators)173 public void setWifiIndicators(@NonNull WifiIndicators indicators) { 174 if (DEBUG) { 175 Log.d(TAG, "setWifiIndicators: " + indicators); 176 } 177 boolean visible = indicators.statusIcon.visible && !mHideWifi; 178 boolean in = indicators.activityIn && mActivityEnabled && visible; 179 boolean out = indicators.activityOut && mActivityEnabled && visible; 180 mIsWifiEnabled = indicators.enabled; 181 182 WifiIconState newState = mWifiIconState.copy(); 183 184 if (mWifiIconState.noDefaultNetwork && mWifiIconState.noNetworksAvailable 185 && !mIsAirplaneMode) { 186 newState.visible = true; 187 newState.resId = R.drawable.ic_qs_no_internet_unavailable; 188 } else if (mWifiIconState.noDefaultNetwork && !mWifiIconState.noNetworksAvailable 189 && (!mIsAirplaneMode || (mIsAirplaneMode && mIsWifiEnabled))) { 190 newState.visible = true; 191 newState.resId = R.drawable.ic_qs_no_internet_available; 192 } else { 193 newState.visible = visible; 194 newState.resId = indicators.statusIcon.icon; 195 newState.activityIn = in; 196 newState.activityOut = out; 197 newState.contentDescription = indicators.statusIcon.contentDescription; 198 MobileIconState first = getFirstMobileState(); 199 newState.signalSpacerVisible = first != null && first.typeId != 0; 200 } 201 newState.slot = mSlotWifi; 202 newState.airplaneSpacerVisible = mIsAirplaneMode; 203 updateWifiIconWithState(newState); 204 mWifiIconState = newState; 205 } 206 updateShowWifiSignalSpacer(WifiIconState state)207 private void updateShowWifiSignalSpacer(WifiIconState state) { 208 MobileIconState first = getFirstMobileState(); 209 state.signalSpacerVisible = first != null && first.typeId != 0; 210 } 211 updateWifiIconWithState(WifiIconState state)212 private void updateWifiIconWithState(WifiIconState state) { 213 if (DEBUG) Log.d(TAG, "WifiIconState: " + state == null ? "" : state.toString()); 214 if (state.visible && state.resId > 0) { 215 mIconController.setWifiIcon(mSlotWifi, state); 216 mIconController.setIconVisibility(mSlotWifi, true); 217 } else { 218 mIconController.setIconVisibility(mSlotWifi, false); 219 } 220 } 221 222 @Override setCallIndicator(@onNull IconState statusIcon, int subId)223 public void setCallIndicator(@NonNull IconState statusIcon, int subId) { 224 if (DEBUG) { 225 Log.d(TAG, "setCallIndicator: " 226 + "statusIcon = " + statusIcon + "," 227 + "subId = " + subId); 228 } 229 CallIndicatorIconState state = getNoCallingState(subId); 230 if (state == null) { 231 return; 232 } 233 if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) { 234 state.isNoCalling = statusIcon.visible; 235 state.noCallingDescription = statusIcon.contentDescription; 236 } else { 237 state.callStrengthResId = statusIcon.icon; 238 state.callStrengthDescription = statusIcon.contentDescription; 239 } 240 if (mCarrierConfigTracker.getCallStrengthConfig(subId)) { 241 mIconController.setCallStrengthIcons(mSlotCallStrength, 242 CallIndicatorIconState.copyStates(mCallIndicatorStates)); 243 } else { 244 mIconController.removeIcon(mSlotCallStrength, subId); 245 } 246 mIconController.setNoCallingIcons(mSlotNoCalling, 247 CallIndicatorIconState.copyStates(mCallIndicatorStates)); 248 } 249 250 @Override setMobileDataIndicators(@onNull MobileDataIndicators indicators)251 public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) { 252 if (DEBUG) { 253 Log.d(TAG, "setMobileDataIndicators: " + indicators); 254 } 255 MobileIconState state = getState(indicators.subId); 256 if (state == null) { 257 return; 258 } 259 260 // Visibility of the data type indicator changed 261 boolean typeChanged = indicators.statusType != state.typeId 262 && (indicators.statusType == 0 || state.typeId == 0); 263 264 state.visible = indicators.statusIcon.visible && !mHideMobile; 265 state.strengthId = indicators.statusIcon.icon; 266 state.typeId = indicators.statusType; 267 state.contentDescription = indicators.statusIcon.contentDescription; 268 state.typeContentDescription = indicators.typeContentDescription; 269 state.showTriangle = indicators.showTriangle; 270 state.roaming = indicators.roaming; 271 state.activityIn = indicators.activityIn && mActivityEnabled; 272 state.activityOut = indicators.activityOut && mActivityEnabled; 273 274 if (DEBUG) { 275 Log.d(TAG, "MobileIconStates: " 276 + (mMobileStates == null ? "" : mMobileStates.toString())); 277 } 278 // Always send a copy to maintain value type semantics 279 mIconController.setMobileIcons(mSlotMobile, MobileIconState.copyStates(mMobileStates)); 280 281 if (typeChanged) { 282 WifiIconState wifiCopy = mWifiIconState.copy(); 283 updateShowWifiSignalSpacer(wifiCopy); 284 if (!Objects.equals(wifiCopy, mWifiIconState)) { 285 updateWifiIconWithState(wifiCopy); 286 mWifiIconState = wifiCopy; 287 } 288 } 289 } 290 getNoCallingState(int subId)291 private CallIndicatorIconState getNoCallingState(int subId) { 292 for (CallIndicatorIconState state : mCallIndicatorStates) { 293 if (state.subId == subId) { 294 return state; 295 } 296 } 297 Log.e(TAG, "Unexpected subscription " + subId); 298 return null; 299 } 300 getState(int subId)301 private MobileIconState getState(int subId) { 302 for (MobileIconState state : mMobileStates) { 303 if (state.subId == subId) { 304 return state; 305 } 306 } 307 Log.e(TAG, "Unexpected subscription " + subId); 308 return null; 309 } 310 getFirstMobileState()311 private MobileIconState getFirstMobileState() { 312 if (mMobileStates.size() > 0) { 313 return mMobileStates.get(0); 314 } 315 316 return null; 317 } 318 319 320 /** 321 * It is expected that a call to setSubs will be immediately followed by setMobileDataIndicators 322 * so we don't have to update the icon manager at this point, just remove the old ones 323 * @param subs list of mobile subscriptions, displayed as mobile data indicators (max 8) 324 */ 325 @Override setSubs(List<SubscriptionInfo> subs)326 public void setSubs(List<SubscriptionInfo> subs) { 327 if (DEBUG) Log.d(TAG, "setSubs: " + (subs == null ? "" : subs.toString())); 328 if (hasCorrectSubs(subs)) { 329 return; 330 } 331 332 mIconController.removeAllIconsForSlot(mSlotMobile); 333 mIconController.removeAllIconsForSlot(mSlotNoCalling); 334 mIconController.removeAllIconsForSlot(mSlotCallStrength); 335 mMobileStates.clear(); 336 List<CallIndicatorIconState> noCallingStates = new ArrayList<CallIndicatorIconState>(); 337 noCallingStates.addAll(mCallIndicatorStates); 338 mCallIndicatorStates.clear(); 339 final int n = subs.size(); 340 for (int i = 0; i < n; i++) { 341 mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId())); 342 boolean isNewSub = true; 343 for (CallIndicatorIconState state : noCallingStates) { 344 if (state.subId == subs.get(i).getSubscriptionId()) { 345 mCallIndicatorStates.add(state); 346 isNewSub = false; 347 break; 348 } 349 } 350 if (isNewSub) { 351 mCallIndicatorStates.add( 352 new CallIndicatorIconState(subs.get(i).getSubscriptionId())); 353 } 354 } 355 } 356 hasCorrectSubs(List<SubscriptionInfo> subs)357 private boolean hasCorrectSubs(List<SubscriptionInfo> subs) { 358 final int N = subs.size(); 359 if (N != mMobileStates.size()) { 360 return false; 361 } 362 for (int i = 0; i < N; i++) { 363 if (mMobileStates.get(i).subId != subs.get(i).getSubscriptionId()) { 364 return false; 365 } 366 } 367 return true; 368 } 369 370 @Override setNoSims(boolean show, boolean simDetected)371 public void setNoSims(boolean show, boolean simDetected) { 372 // Noop yay! 373 } 374 375 @Override setEthernetIndicators(IconState state)376 public void setEthernetIndicators(IconState state) { 377 boolean visible = state.visible && !mHideEthernet; 378 int resId = state.icon; 379 String description = state.contentDescription; 380 381 if (resId > 0) { 382 mIconController.setIcon(mSlotEthernet, resId, description); 383 mIconController.setIconVisibility(mSlotEthernet, true); 384 } else { 385 mIconController.setIconVisibility(mSlotEthernet, false); 386 } 387 } 388 389 @Override setIsAirplaneMode(IconState icon)390 public void setIsAirplaneMode(IconState icon) { 391 if (DEBUG) { 392 Log.d(TAG, "setIsAirplaneMode: " 393 + "icon = " + (icon == null ? "" : icon.toString())); 394 } 395 mIsAirplaneMode = icon.visible && !mHideAirplane; 396 int resId = icon.icon; 397 String description = icon.contentDescription; 398 399 if (mIsAirplaneMode && resId > 0) { 400 mIconController.setIcon(mSlotAirplane, resId, description); 401 mIconController.setIconVisibility(mSlotAirplane, true); 402 } else { 403 mIconController.setIconVisibility(mSlotAirplane, false); 404 } 405 } 406 407 @Override setMobileDataEnabled(boolean enabled)408 public void setMobileDataEnabled(boolean enabled) { 409 // Don't care. 410 } 411 412 /** 413 * Stores the statusbar state for no Calling & SMS. 414 */ 415 public static class CallIndicatorIconState { 416 public boolean isNoCalling; 417 public int noCallingResId; 418 public int callStrengthResId; 419 public int subId; 420 public String noCallingDescription; 421 public String callStrengthDescription; 422 CallIndicatorIconState(int subId)423 private CallIndicatorIconState(int subId) { 424 this.subId = subId; 425 this.noCallingResId = R.drawable.ic_qs_no_calling_sms; 426 this.callStrengthResId = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0]; 427 } 428 429 @Override equals(Object o)430 public boolean equals(Object o) { 431 // Skipping reference equality bc this should be more of a value type 432 if (o == null || getClass() != o.getClass()) { 433 return false; 434 } 435 CallIndicatorIconState that = (CallIndicatorIconState) o; 436 return isNoCalling == that.isNoCalling 437 && noCallingResId == that.noCallingResId 438 && callStrengthResId == that.callStrengthResId 439 && subId == that.subId 440 && noCallingDescription == that.noCallingDescription 441 && callStrengthDescription == that.callStrengthDescription; 442 443 } 444 445 @Override hashCode()446 public int hashCode() { 447 return Objects.hash(isNoCalling, noCallingResId, 448 callStrengthResId, subId, noCallingDescription, callStrengthDescription); 449 } 450 copyTo(CallIndicatorIconState other)451 private void copyTo(CallIndicatorIconState other) { 452 other.isNoCalling = isNoCalling; 453 other.noCallingResId = noCallingResId; 454 other.callStrengthResId = callStrengthResId; 455 other.subId = subId; 456 other.noCallingDescription = noCallingDescription; 457 other.callStrengthDescription = callStrengthDescription; 458 } 459 copyStates( List<CallIndicatorIconState> inStates)460 private static List<CallIndicatorIconState> copyStates( 461 List<CallIndicatorIconState> inStates) { 462 ArrayList<CallIndicatorIconState> outStates = new ArrayList<>(); 463 for (CallIndicatorIconState state : inStates) { 464 CallIndicatorIconState copy = new CallIndicatorIconState(state.subId); 465 state.copyTo(copy); 466 outStates.add(copy); 467 } 468 return outStates; 469 } 470 } 471 472 private static abstract class SignalIconState { 473 public boolean visible; 474 public boolean activityOut; 475 public boolean activityIn; 476 public String slot; 477 public String contentDescription; 478 479 @Override equals(Object o)480 public boolean equals(Object o) { 481 // Skipping reference equality bc this should be more of a value type 482 if (o == null || getClass() != o.getClass()) { 483 return false; 484 } 485 SignalIconState that = (SignalIconState) o; 486 return visible == that.visible && 487 activityOut == that.activityOut && 488 activityIn == that.activityIn && 489 Objects.equals(contentDescription, that.contentDescription) && 490 Objects.equals(slot, that.slot); 491 } 492 493 @Override hashCode()494 public int hashCode() { 495 return Objects.hash(visible, activityOut, slot); 496 } 497 copyTo(SignalIconState other)498 protected void copyTo(SignalIconState other) { 499 other.visible = visible; 500 other.activityIn = activityIn; 501 other.activityOut = activityOut; 502 other.slot = slot; 503 other.contentDescription = contentDescription; 504 } 505 } 506 507 public static class WifiIconState extends SignalIconState{ 508 public int resId; 509 public boolean airplaneSpacerVisible; 510 public boolean signalSpacerVisible; 511 public boolean noDefaultNetwork; 512 public boolean noValidatedNetwork; 513 public boolean noNetworksAvailable; 514 515 @Override equals(Object o)516 public boolean equals(Object o) { 517 // Skipping reference equality bc this should be more of a value type 518 if (o == null || getClass() != o.getClass()) { 519 return false; 520 } 521 if (!super.equals(o)) { 522 return false; 523 } 524 WifiIconState that = (WifiIconState) o; 525 return resId == that.resId 526 && airplaneSpacerVisible == that.airplaneSpacerVisible 527 && signalSpacerVisible == that.signalSpacerVisible 528 && noDefaultNetwork == that.noDefaultNetwork 529 && noValidatedNetwork == that.noValidatedNetwork 530 && noNetworksAvailable == that.noNetworksAvailable; 531 } 532 copyTo(WifiIconState other)533 public void copyTo(WifiIconState other) { 534 super.copyTo(other); 535 other.resId = resId; 536 other.airplaneSpacerVisible = airplaneSpacerVisible; 537 other.signalSpacerVisible = signalSpacerVisible; 538 other.noDefaultNetwork = noDefaultNetwork; 539 other.noValidatedNetwork = noValidatedNetwork; 540 other.noNetworksAvailable = noNetworksAvailable; 541 } 542 copy()543 public WifiIconState copy() { 544 WifiIconState newState = new WifiIconState(); 545 copyTo(newState); 546 return newState; 547 } 548 549 @Override hashCode()550 public int hashCode() { 551 return Objects.hash(super.hashCode(), 552 resId, airplaneSpacerVisible, signalSpacerVisible, noDefaultNetwork, 553 noValidatedNetwork, noNetworksAvailable); 554 } 555 toString()556 @Override public String toString() { 557 return "WifiIconState(resId=" + resId + ", visible=" + visible + ")"; 558 } 559 } 560 561 /** 562 * A little different. This one delegates to SignalDrawable instead of a specific resId 563 */ 564 public static class MobileIconState extends SignalIconState { 565 public int subId; 566 public int strengthId; 567 public int typeId; 568 public boolean showTriangle; 569 public boolean roaming; 570 public boolean needsLeadingPadding; 571 public CharSequence typeContentDescription; 572 MobileIconState(int subId)573 private MobileIconState(int subId) { 574 super(); 575 this.subId = subId; 576 } 577 578 @Override equals(Object o)579 public boolean equals(Object o) { 580 if (o == null || getClass() != o.getClass()) { 581 return false; 582 } 583 if (!super.equals(o)) { 584 return false; 585 } 586 MobileIconState that = (MobileIconState) o; 587 return subId == that.subId 588 && strengthId == that.strengthId 589 && typeId == that.typeId 590 && showTriangle == that.showTriangle 591 && roaming == that.roaming 592 && needsLeadingPadding == that.needsLeadingPadding 593 && Objects.equals(typeContentDescription, that.typeContentDescription); 594 } 595 596 @Override hashCode()597 public int hashCode() { 598 599 return Objects 600 .hash(super.hashCode(), subId, strengthId, typeId, showTriangle, roaming, 601 needsLeadingPadding, typeContentDescription); 602 } 603 copy()604 public MobileIconState copy() { 605 MobileIconState copy = new MobileIconState(this.subId); 606 copyTo(copy); 607 return copy; 608 } 609 copyTo(MobileIconState other)610 public void copyTo(MobileIconState other) { 611 super.copyTo(other); 612 other.subId = subId; 613 other.strengthId = strengthId; 614 other.typeId = typeId; 615 other.showTriangle = showTriangle; 616 other.roaming = roaming; 617 other.needsLeadingPadding = needsLeadingPadding; 618 other.typeContentDescription = typeContentDescription; 619 } 620 copyStates(List<MobileIconState> inStates)621 private static List<MobileIconState> copyStates(List<MobileIconState> inStates) { 622 ArrayList<MobileIconState> outStates = new ArrayList<>(); 623 for (MobileIconState state : inStates) { 624 MobileIconState copy = new MobileIconState(state.subId); 625 state.copyTo(copy); 626 outStates.add(copy); 627 } 628 629 return outStates; 630 } 631 toString()632 @Override public String toString() { 633 return "MobileIconState(subId=" + subId + ", strengthId=" + strengthId 634 + ", showTriangle=" + showTriangle + ", roaming=" + roaming 635 + ", typeId=" + typeId + ", visible=" + visible + ")"; 636 } 637 } 638 } 639