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