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