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