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