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.systemui.Dependency;
26 import com.android.systemui.R;
27 import com.android.systemui.statusbar.policy.NetworkController;
28 import com.android.systemui.statusbar.policy.NetworkController.IconState;
29 import com.android.systemui.statusbar.policy.NetworkControllerImpl;
30 import com.android.systemui.statusbar.policy.SecurityController;
31 import com.android.systemui.tuner.TunerService;
32 import com.android.systemui.tuner.TunerService.Tunable;
33 
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.Objects;
37 
38 
39 public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallback,
40         SecurityController.SecurityControllerCallback, Tunable {
41     private static final String TAG = "StatusBarSignalPolicy";
42 
43     private final String mSlotAirplane;
44     private final String mSlotMobile;
45     private final String mSlotWifi;
46     private final String mSlotEthernet;
47     private final String mSlotVpn;
48 
49     private final Context mContext;
50     private final StatusBarIconController mIconController;
51     private final NetworkController mNetworkController;
52     private final SecurityController mSecurityController;
53     private final Handler mHandler = Handler.getMain();
54 
55     private boolean mBlockAirplane;
56     private boolean mBlockMobile;
57     private boolean mBlockWifi;
58     private boolean mBlockEthernet;
59     private boolean mActivityEnabled;
60     private boolean mForceBlockWifi;
61 
62     // Track as little state as possible, and only for padding purposes
63     private boolean mIsAirplaneMode = false;
64     private boolean mWifiVisible = false;
65 
66     private ArrayList<MobileIconState> mMobileStates = new ArrayList<MobileIconState>();
67     private WifiIconState mWifiIconState = new WifiIconState();
68 
StatusBarSignalPolicy(Context context, StatusBarIconController iconController)69     public StatusBarSignalPolicy(Context context, StatusBarIconController iconController) {
70         mContext = context;
71 
72         mSlotAirplane = mContext.getString(com.android.internal.R.string.status_bar_airplane);
73         mSlotMobile   = mContext.getString(com.android.internal.R.string.status_bar_mobile);
74         mSlotWifi     = mContext.getString(com.android.internal.R.string.status_bar_wifi);
75         mSlotEthernet = mContext.getString(com.android.internal.R.string.status_bar_ethernet);
76         mSlotVpn      = mContext.getString(com.android.internal.R.string.status_bar_vpn);
77         mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity);
78 
79         mIconController = iconController;
80         mNetworkController = Dependency.get(NetworkController.class);
81         mSecurityController = Dependency.get(SecurityController.class);
82 
83         Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
84         mNetworkController.addCallback(this);
85         mSecurityController.addCallback(this);
86     }
87 
destroy()88     public void destroy() {
89         Dependency.get(TunerService.class).removeTunable(this);
90         mNetworkController.removeCallback(this);
91         mSecurityController.removeCallback(this);
92     }
93 
updateVpn()94     private void updateVpn() {
95         boolean vpnVisible = mSecurityController.isVpnEnabled();
96         int vpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
97 
98         mIconController.setIcon(mSlotVpn, vpnIconId,
99                 mContext.getResources().getString(R.string.accessibility_vpn_on));
100         mIconController.setIconVisibility(mSlotVpn, vpnVisible);
101     }
102 
currentVpnIconId(boolean isBranded)103     private int currentVpnIconId(boolean isBranded) {
104         return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic;
105     }
106 
107     /**
108      * From SecurityController
109      */
110     @Override
onStateChanged()111     public void onStateChanged() {
112         mHandler.post(this::updateVpn);
113     }
114 
115     @Override
onTuningChanged(String key, String newValue)116     public void onTuningChanged(String key, String newValue) {
117         if (!StatusBarIconController.ICON_BLACKLIST.equals(key)) {
118             return;
119         }
120         ArraySet<String> blockList = StatusBarIconController.getIconBlacklist(newValue);
121         boolean blockAirplane = blockList.contains(mSlotAirplane);
122         boolean blockMobile = blockList.contains(mSlotMobile);
123         boolean blockWifi = blockList.contains(mSlotWifi);
124         boolean blockEthernet = blockList.contains(mSlotEthernet);
125 
126         if (blockAirplane != mBlockAirplane || blockMobile != mBlockMobile
127                 || blockEthernet != mBlockEthernet || blockWifi != mBlockWifi) {
128             mBlockAirplane = blockAirplane;
129             mBlockMobile = blockMobile;
130             mBlockEthernet = blockEthernet;
131             mBlockWifi = blockWifi || mForceBlockWifi;
132             // Re-register to get new callbacks.
133             mNetworkController.removeCallback(this);
134             mNetworkController.addCallback(this);
135         }
136     }
137 
138     @Override
setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon, boolean activityIn, boolean activityOut, String description, boolean isTransient, String statusLabel)139     public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
140             boolean activityIn, boolean activityOut, String description, boolean isTransient,
141             String statusLabel) {
142 
143         boolean visible = statusIcon.visible && !mBlockWifi;
144         boolean in = activityIn && mActivityEnabled && visible;
145         boolean out = activityOut && mActivityEnabled && visible;
146 
147         WifiIconState newState = mWifiIconState.copy();
148 
149         newState.visible = visible;
150         newState.resId = statusIcon.icon;
151         newState.activityIn = in;
152         newState.activityOut = out;
153         newState.slot = mSlotWifi;
154         newState.airplaneSpacerVisible = mIsAirplaneMode;
155         newState.contentDescription = statusIcon.contentDescription;
156 
157         MobileIconState first = getFirstMobileState();
158         newState.signalSpacerVisible = first != null && first.typeId != 0;
159 
160         updateWifiIconWithState(newState);
161         mWifiIconState = newState;
162     }
163 
updateShowWifiSignalSpacer(WifiIconState state)164     private void updateShowWifiSignalSpacer(WifiIconState state) {
165         MobileIconState first = getFirstMobileState();
166         state.signalSpacerVisible = first != null && first.typeId != 0;
167     }
168 
updateWifiIconWithState(WifiIconState state)169     private void updateWifiIconWithState(WifiIconState state) {
170         if (state.visible && state.resId > 0) {
171             mIconController.setSignalIcon(mSlotWifi, state);
172             mIconController.setIconVisibility(mSlotWifi, true);
173         } else {
174             mIconController.setIconVisibility(mSlotWifi, false);
175         }
176     }
177 
178     @Override
setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType, int qsType, boolean activityIn, boolean activityOut, CharSequence typeContentDescription, CharSequence typeContentDescriptionHtml, CharSequence description, boolean isWide, int subId, boolean roaming)179     public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
180             int qsType, boolean activityIn, boolean activityOut,
181             CharSequence typeContentDescription,
182             CharSequence typeContentDescriptionHtml, CharSequence description,
183             boolean isWide, int subId, boolean roaming) {
184         MobileIconState state = getState(subId);
185         if (state == null) {
186             return;
187         }
188 
189         // Visibility of the data type indicator changed
190         boolean typeChanged = statusType != state.typeId && (statusType == 0 || state.typeId == 0);
191 
192         state.visible = statusIcon.visible && !mBlockMobile;
193         state.strengthId = statusIcon.icon;
194         state.typeId = statusType;
195         state.contentDescription = statusIcon.contentDescription;
196         state.typeContentDescription = typeContentDescription;
197         state.roaming = roaming;
198         state.activityIn = activityIn && mActivityEnabled;
199         state.activityOut = activityOut && mActivityEnabled;
200 
201         // Always send a copy to maintain value type semantics
202         mIconController.setMobileIcons(mSlotMobile, MobileIconState.copyStates(mMobileStates));
203 
204         if (typeChanged) {
205             WifiIconState wifiCopy = mWifiIconState.copy();
206             updateShowWifiSignalSpacer(wifiCopy);
207             if (!Objects.equals(wifiCopy, mWifiIconState)) {
208                 updateWifiIconWithState(wifiCopy);
209                 mWifiIconState = wifiCopy;
210             }
211         }
212     }
213 
getState(int subId)214     private MobileIconState getState(int subId) {
215         for (MobileIconState state : mMobileStates) {
216             if (state.subId == subId) {
217                 return state;
218             }
219         }
220         Log.e(TAG, "Unexpected subscription " + subId);
221         return null;
222     }
223 
getFirstMobileState()224     private MobileIconState getFirstMobileState() {
225         if (mMobileStates.size() > 0) {
226             return mMobileStates.get(0);
227         }
228 
229         return null;
230     }
231 
232 
233     /**
234      * It is expected that a call to setSubs will be immediately followed by setMobileDataIndicators
235      * so we don't have to update the icon manager at this point, just remove the old ones
236      * @param subs list of mobile subscriptions, displayed as mobile data indicators (max 8)
237      */
238     @Override
setSubs(List<SubscriptionInfo> subs)239     public void setSubs(List<SubscriptionInfo> subs) {
240         if (hasCorrectSubs(subs)) {
241             return;
242         }
243 
244         mIconController.removeAllIconsForSlot(mSlotMobile);
245         mMobileStates.clear();
246         final int n = subs.size();
247         for (int i = 0; i < n; i++) {
248             mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId()));
249         }
250     }
251 
hasCorrectSubs(List<SubscriptionInfo> subs)252     private boolean hasCorrectSubs(List<SubscriptionInfo> subs) {
253         final int N = subs.size();
254         if (N != mMobileStates.size()) {
255             return false;
256         }
257         for (int i = 0; i < N; i++) {
258             if (mMobileStates.get(i).subId != subs.get(i).getSubscriptionId()) {
259                 return false;
260             }
261         }
262         return true;
263     }
264 
265     @Override
setNoSims(boolean show, boolean simDetected)266     public void setNoSims(boolean show, boolean simDetected) {
267         // Noop yay!
268     }
269 
270 
271     @Override
setEthernetIndicators(IconState state)272     public void setEthernetIndicators(IconState state) {
273         boolean visible = state.visible && !mBlockEthernet;
274         int resId = state.icon;
275         String description = state.contentDescription;
276 
277         if (resId > 0) {
278             mIconController.setIcon(mSlotEthernet, resId, description);
279             mIconController.setIconVisibility(mSlotEthernet, true);
280         } else {
281             mIconController.setIconVisibility(mSlotEthernet, false);
282         }
283     }
284 
285     @Override
setIsAirplaneMode(IconState icon)286     public void setIsAirplaneMode(IconState icon) {
287         mIsAirplaneMode = icon.visible && !mBlockAirplane;
288         int resId = icon.icon;
289         String description = icon.contentDescription;
290 
291         if (mIsAirplaneMode && resId > 0) {
292             mIconController.setIcon(mSlotAirplane, resId, description);
293             mIconController.setIconVisibility(mSlotAirplane, true);
294         } else {
295             mIconController.setIconVisibility(mSlotAirplane, false);
296         }
297     }
298 
299     @Override
setMobileDataEnabled(boolean enabled)300     public void setMobileDataEnabled(boolean enabled) {
301         // Don't care.
302     }
303 
304     private static abstract class SignalIconState {
305         public boolean visible;
306         public boolean activityOut;
307         public boolean activityIn;
308         public String slot;
309         public String contentDescription;
310 
311         @Override
equals(Object o)312         public boolean equals(Object o) {
313             // Skipping reference equality bc this should be more of a value type
314             if (o == null || getClass() != o.getClass()) {
315                 return false;
316             }
317             SignalIconState that = (SignalIconState) o;
318             return visible == that.visible &&
319                     activityOut == that.activityOut &&
320                     activityIn == that.activityIn &&
321                     Objects.equals(contentDescription, that.contentDescription) &&
322                     Objects.equals(slot, that.slot);
323         }
324 
325         @Override
hashCode()326         public int hashCode() {
327             return Objects.hash(visible, activityOut, slot);
328         }
329 
copyTo(SignalIconState other)330         protected void copyTo(SignalIconState other) {
331             other.visible = visible;
332             other.activityIn = activityIn;
333             other.activityOut = activityOut;
334             other.slot = slot;
335             other.contentDescription = contentDescription;
336         }
337     }
338 
339     public static class WifiIconState extends SignalIconState{
340         public int resId;
341         public boolean airplaneSpacerVisible;
342         public boolean signalSpacerVisible;
343 
344         @Override
equals(Object o)345         public boolean equals(Object o) {
346             // Skipping reference equality bc this should be more of a value type
347             if (o == null || getClass() != o.getClass()) {
348                 return false;
349             }
350             if (!super.equals(o)) {
351                 return false;
352             }
353             WifiIconState that = (WifiIconState) o;
354             return resId == that.resId &&
355                     airplaneSpacerVisible == that.airplaneSpacerVisible &&
356                     signalSpacerVisible == that.signalSpacerVisible;
357         }
358 
copyTo(WifiIconState other)359         public void copyTo(WifiIconState other) {
360             super.copyTo(other);
361             other.resId = resId;
362             other.airplaneSpacerVisible = airplaneSpacerVisible;
363             other.signalSpacerVisible = signalSpacerVisible;
364         }
365 
copy()366         public WifiIconState copy() {
367             WifiIconState newState = new WifiIconState();
368             copyTo(newState);
369             return newState;
370         }
371 
372         @Override
hashCode()373         public int hashCode() {
374             return Objects.hash(super.hashCode(),
375                     resId, airplaneSpacerVisible, signalSpacerVisible);
376         }
377 
toString()378         @Override public String toString() {
379             return "WifiIconState(resId=" + resId + ", visible=" + visible + ")";
380         }
381     }
382 
383     /**
384      * A little different. This one delegates to SignalDrawable instead of a specific resId
385      */
386     public static class MobileIconState extends SignalIconState {
387         public int subId;
388         public int strengthId;
389         public int typeId;
390         public boolean roaming;
391         public boolean needsLeadingPadding;
392         public CharSequence typeContentDescription;
393 
MobileIconState(int subId)394         private MobileIconState(int subId) {
395             super();
396             this.subId = subId;
397         }
398 
399         @Override
equals(Object o)400         public boolean equals(Object o) {
401             if (o == null || getClass() != o.getClass()) {
402                 return false;
403             }
404             if (!super.equals(o)) {
405                 return false;
406             }
407             MobileIconState that = (MobileIconState) o;
408             return subId == that.subId &&
409                     strengthId == that.strengthId &&
410                     typeId == that.typeId &&
411                     roaming == that.roaming &&
412                     needsLeadingPadding == that.needsLeadingPadding &&
413                     Objects.equals(typeContentDescription, that.typeContentDescription);
414         }
415 
416         @Override
hashCode()417         public int hashCode() {
418 
419             return Objects
420                     .hash(super.hashCode(), subId, strengthId, typeId, roaming, needsLeadingPadding,
421                             typeContentDescription);
422         }
423 
copy()424         public MobileIconState copy() {
425             MobileIconState copy = new MobileIconState(this.subId);
426             copyTo(copy);
427             return copy;
428         }
429 
copyTo(MobileIconState other)430         public void copyTo(MobileIconState other) {
431             super.copyTo(other);
432             other.subId = subId;
433             other.strengthId = strengthId;
434             other.typeId = typeId;
435             other.roaming = roaming;
436             other.needsLeadingPadding = needsLeadingPadding;
437             other.typeContentDescription = typeContentDescription;
438         }
439 
copyStates(List<MobileIconState> inStates)440         private static List<MobileIconState> copyStates(List<MobileIconState> inStates) {
441             ArrayList<MobileIconState> outStates = new ArrayList<>();
442             for (MobileIconState state : inStates) {
443                 MobileIconState copy = new MobileIconState(state.subId);
444                 state.copyTo(copy);
445                 outStates.add(copy);
446             }
447 
448             return outStates;
449         }
450 
toString()451         @Override public String toString() {
452             return "MobileIconState(subId=" + subId + ", strengthId=" + strengthId + ", roaming="
453                     + roaming + ", typeId=" + typeId + ", visible=" + visible + ")";
454         }
455     }
456 }
457