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 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 95 public void destroy() { 96 mNetworkController.removeCallback(this); 97 mSecurityController.removeCallback(this); 98 } 99 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 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 116 public void onStateChanged() { 117 mHandler.post(this::updateVpn); 118 } 119 120 @Override 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 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 168 private void updateShowWifiSignalSpacer(WifiIconState state) { 169 MobileIconState first = getFirstMobileState(); 170 state.signalSpacerVisible = first != null && first.typeId != 0; 171 } 172 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 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 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 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 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 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 268 public void setNoSims(boolean show, boolean simDetected) { 269 // Noop yay! 270 } 271 272 273 @Override 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 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 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 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 328 public int hashCode() { 329 return Objects.hash(visible, activityOut, slot); 330 } 331 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 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 361 public void copyTo(WifiIconState other) { 362 super.copyTo(other); 363 other.resId = resId; 364 other.airplaneSpacerVisible = airplaneSpacerVisible; 365 other.signalSpacerVisible = signalSpacerVisible; 366 } 367 368 public WifiIconState copy() { 369 WifiIconState newState = new WifiIconState(); 370 copyTo(newState); 371 return newState; 372 } 373 374 @Override 375 public int hashCode() { 376 return Objects.hash(super.hashCode(), 377 resId, airplaneSpacerVisible, signalSpacerVisible); 378 } 379 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 396 private MobileIconState(int subId) { 397 super(); 398 this.subId = subId; 399 } 400 401 @Override 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 419 public int hashCode() { 420 421 return Objects 422 .hash(super.hashCode(), subId, strengthId, typeId, roaming, needsLeadingPadding, 423 typeContentDescription); 424 } 425 426 public MobileIconState copy() { 427 MobileIconState copy = new MobileIconState(this.subId); 428 copyTo(copy); 429 return copy; 430 } 431 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 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 453 @Override public String toString() { 454 return "MobileIconState(subId=" + subId + ", strengthId=" + strengthId + ", roaming=" 455 + roaming + ", typeId=" + typeId + ", visible=" + visible + ")"; 456 } 457 } 458 } 459