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