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, String typeContentDescription, String description, boolean isWide, int subId, boolean roaming)179 public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType, 180 int qsType, boolean activityIn, boolean activityOut, String typeContentDescription, 181 String description, boolean isWide, int subId, boolean roaming) { 182 MobileIconState state = getState(subId); 183 if (state == null) { 184 return; 185 } 186 187 // Visibility of the data type indicator changed 188 boolean typeChanged = statusType != state.typeId && (statusType == 0 || state.typeId == 0); 189 190 state.visible = statusIcon.visible && !mBlockMobile; 191 state.strengthId = statusIcon.icon; 192 state.typeId = statusType; 193 state.contentDescription = statusIcon.contentDescription; 194 state.typeContentDescription = typeContentDescription; 195 state.roaming = roaming; 196 state.activityIn = activityIn && mActivityEnabled; 197 state.activityOut = activityOut && mActivityEnabled; 198 199 // Always send a copy to maintain value type semantics 200 mIconController.setMobileIcons(mSlotMobile, MobileIconState.copyStates(mMobileStates)); 201 202 if (typeChanged) { 203 WifiIconState wifiCopy = mWifiIconState.copy(); 204 updateShowWifiSignalSpacer(wifiCopy); 205 if (!Objects.equals(wifiCopy, mWifiIconState)) { 206 updateWifiIconWithState(wifiCopy); 207 mWifiIconState = wifiCopy; 208 } 209 } 210 } 211 getState(int subId)212 private MobileIconState getState(int subId) { 213 for (MobileIconState state : mMobileStates) { 214 if (state.subId == subId) { 215 return state; 216 } 217 } 218 Log.e(TAG, "Unexpected subscription " + subId); 219 return null; 220 } 221 getFirstMobileState()222 private MobileIconState getFirstMobileState() { 223 if (mMobileStates.size() > 0) { 224 return mMobileStates.get(0); 225 } 226 227 return null; 228 } 229 230 231 /** 232 * It is expected that a call to setSubs will be immediately followed by setMobileDataIndicators 233 * so we don't have to update the icon manager at this point, just remove the old ones 234 * @param subs list of mobile subscriptions, displayed as mobile data indicators (max 8) 235 */ 236 @Override setSubs(List<SubscriptionInfo> subs)237 public void setSubs(List<SubscriptionInfo> subs) { 238 if (hasCorrectSubs(subs)) { 239 return; 240 } 241 242 mIconController.removeAllIconsForSlot(mSlotMobile); 243 mMobileStates.clear(); 244 final int n = subs.size(); 245 for (int i = 0; i < n; i++) { 246 mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId())); 247 } 248 } 249 hasCorrectSubs(List<SubscriptionInfo> subs)250 private boolean hasCorrectSubs(List<SubscriptionInfo> subs) { 251 final int N = subs.size(); 252 if (N != mMobileStates.size()) { 253 return false; 254 } 255 for (int i = 0; i < N; i++) { 256 if (mMobileStates.get(i).subId != subs.get(i).getSubscriptionId()) { 257 return false; 258 } 259 } 260 return true; 261 } 262 263 @Override setNoSims(boolean show, boolean simDetected)264 public void setNoSims(boolean show, boolean simDetected) { 265 // Noop yay! 266 } 267 268 269 @Override setEthernetIndicators(IconState state)270 public void setEthernetIndicators(IconState state) { 271 boolean visible = state.visible && !mBlockEthernet; 272 int resId = state.icon; 273 String description = state.contentDescription; 274 275 if (resId > 0) { 276 mIconController.setIcon(mSlotEthernet, resId, description); 277 mIconController.setIconVisibility(mSlotEthernet, true); 278 } else { 279 mIconController.setIconVisibility(mSlotEthernet, false); 280 } 281 } 282 283 @Override setIsAirplaneMode(IconState icon)284 public void setIsAirplaneMode(IconState icon) { 285 mIsAirplaneMode = icon.visible && !mBlockAirplane; 286 int resId = icon.icon; 287 String description = icon.contentDescription; 288 289 if (mIsAirplaneMode && resId > 0) { 290 mIconController.setIcon(mSlotAirplane, resId, description); 291 mIconController.setIconVisibility(mSlotAirplane, true); 292 } else { 293 mIconController.setIconVisibility(mSlotAirplane, false); 294 } 295 } 296 297 @Override setMobileDataEnabled(boolean enabled)298 public void setMobileDataEnabled(boolean enabled) { 299 // Don't care. 300 } 301 302 private static abstract class SignalIconState { 303 public boolean visible; 304 public boolean activityOut; 305 public boolean activityIn; 306 public String slot; 307 public String contentDescription; 308 309 @Override equals(Object o)310 public boolean equals(Object o) { 311 // Skipping reference equality bc this should be more of a value type 312 if (o == null || getClass() != o.getClass()) { 313 return false; 314 } 315 SignalIconState that = (SignalIconState) o; 316 return visible == that.visible && 317 activityOut == that.activityOut && 318 activityIn == that.activityIn && 319 Objects.equals(contentDescription, that.contentDescription) && 320 Objects.equals(slot, that.slot); 321 } 322 323 @Override hashCode()324 public int hashCode() { 325 return Objects.hash(visible, activityOut, slot); 326 } 327 copyTo(SignalIconState other)328 protected void copyTo(SignalIconState other) { 329 other.visible = visible; 330 other.activityIn = activityIn; 331 other.activityOut = activityOut; 332 other.slot = slot; 333 other.contentDescription = contentDescription; 334 } 335 } 336 337 public static class WifiIconState extends SignalIconState{ 338 public int resId; 339 public boolean airplaneSpacerVisible; 340 public boolean signalSpacerVisible; 341 342 @Override equals(Object o)343 public boolean equals(Object o) { 344 // Skipping reference equality bc this should be more of a value type 345 if (o == null || getClass() != o.getClass()) { 346 return false; 347 } 348 if (!super.equals(o)) { 349 return false; 350 } 351 WifiIconState that = (WifiIconState) o; 352 return resId == that.resId && 353 airplaneSpacerVisible == that.airplaneSpacerVisible && 354 signalSpacerVisible == that.signalSpacerVisible; 355 } 356 copyTo(WifiIconState other)357 public void copyTo(WifiIconState other) { 358 super.copyTo(other); 359 other.resId = resId; 360 other.airplaneSpacerVisible = airplaneSpacerVisible; 361 other.signalSpacerVisible = signalSpacerVisible; 362 } 363 copy()364 public WifiIconState copy() { 365 WifiIconState newState = new WifiIconState(); 366 copyTo(newState); 367 return newState; 368 } 369 370 @Override hashCode()371 public int hashCode() { 372 return Objects.hash(super.hashCode(), 373 resId, airplaneSpacerVisible, signalSpacerVisible); 374 } 375 toString()376 @Override public String toString() { 377 return "WifiIconState(resId=" + resId + ", visible=" + visible + ")"; 378 } 379 } 380 381 /** 382 * A little different. This one delegates to SignalDrawable instead of a specific resId 383 */ 384 public static class MobileIconState extends SignalIconState { 385 public int subId; 386 public int strengthId; 387 public int typeId; 388 public boolean roaming; 389 public boolean needsLeadingPadding; 390 public String typeContentDescription; 391 MobileIconState(int subId)392 private MobileIconState(int subId) { 393 super(); 394 this.subId = subId; 395 } 396 397 @Override equals(Object o)398 public boolean equals(Object o) { 399 if (o == null || getClass() != o.getClass()) { 400 return false; 401 } 402 if (!super.equals(o)) { 403 return false; 404 } 405 MobileIconState that = (MobileIconState) o; 406 return subId == that.subId && 407 strengthId == that.strengthId && 408 typeId == that.typeId && 409 roaming == that.roaming && 410 needsLeadingPadding == that.needsLeadingPadding && 411 Objects.equals(typeContentDescription, that.typeContentDescription); 412 } 413 414 @Override hashCode()415 public int hashCode() { 416 417 return Objects 418 .hash(super.hashCode(), subId, strengthId, typeId, roaming, needsLeadingPadding, 419 typeContentDescription); 420 } 421 copy()422 public MobileIconState copy() { 423 MobileIconState copy = new MobileIconState(this.subId); 424 copyTo(copy); 425 return copy; 426 } 427 copyTo(MobileIconState other)428 public void copyTo(MobileIconState other) { 429 super.copyTo(other); 430 other.subId = subId; 431 other.strengthId = strengthId; 432 other.typeId = typeId; 433 other.roaming = roaming; 434 other.needsLeadingPadding = needsLeadingPadding; 435 other.typeContentDescription = typeContentDescription; 436 } 437 copyStates(List<MobileIconState> inStates)438 private static List<MobileIconState> copyStates(List<MobileIconState> inStates) { 439 ArrayList<MobileIconState> outStates = new ArrayList<>(); 440 for (MobileIconState state : inStates) { 441 MobileIconState copy = new MobileIconState(state.subId); 442 state.copyTo(copy); 443 outStates.add(copy); 444 } 445 446 return outStates; 447 } 448 toString()449 @Override public String toString() { 450 return "MobileIconState(subId=" + subId + ", strengthId=" + strengthId + ", roaming=" 451 + roaming + ", typeId=" + typeId + ", visible=" + visible + ")"; 452 } 453 } 454 } 455