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