Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 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; |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 20 | import android.os.Handler; |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 21 | import android.telephony.SubscriptionInfo; |
| 22 | import android.util.ArraySet; |
| 23 | import android.util.Log; |
Gus Prevas | ab33679 | 2018-11-14 13:52:20 -0500 | [diff] [blame] | 24 | |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 25 | import com.android.systemui.Dependency; |
| 26 | import com.android.systemui.R; |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 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; |
Luca Stefani | 4e41142 | 2018-08-31 22:05:08 +0200 | [diff] [blame] | 31 | import com.android.systemui.tuner.TunerService; |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 32 | import com.android.systemui.tuner.TunerService.Tunable; |
Gus Prevas | ab33679 | 2018-11-14 13:52:20 -0500 | [diff] [blame] | 33 | |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 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 | |
| 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); |
Evan Laird | 6fd0417 | 2018-04-23 11:37:04 -0400 | [diff] [blame] | 77 | mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity); |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 78 | |
| 79 | mIconController = iconController; |
| 80 | mNetworkController = Dependency.get(NetworkController.class); |
| 81 | mSecurityController = Dependency.get(SecurityController.class); |
| 82 | |
Luca Stefani | 4e41142 | 2018-08-31 22:05:08 +0200 | [diff] [blame] | 83 | Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST); |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 84 | mNetworkController.addCallback(this); |
| 85 | mSecurityController.addCallback(this); |
| 86 | } |
| 87 | |
| 88 | public void destroy() { |
Luca Stefani | 4e41142 | 2018-08-31 22:05:08 +0200 | [diff] [blame] | 89 | Dependency.get(TunerService.class).removeTunable(this); |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 90 | mNetworkController.removeCallback(this); |
| 91 | mSecurityController.removeCallback(this); |
| 92 | } |
| 93 | |
| 94 | private void updateVpn() { |
| 95 | boolean vpnVisible = mSecurityController.isVpnEnabled(); |
| 96 | int vpnIconId = currentVpnIconId(mSecurityController.isVpnBranded()); |
| 97 | |
Evan Laird | c5b79d9 | 2018-10-29 14:08:14 -0400 | [diff] [blame] | 98 | mIconController.setIcon(mSlotVpn, vpnIconId, |
| 99 | mContext.getResources().getString(R.string.accessibility_vpn_on)); |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 100 | mIconController.setIconVisibility(mSlotVpn, vpnVisible); |
| 101 | } |
| 102 | |
| 103 | private int currentVpnIconId(boolean isBranded) { |
| 104 | return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic; |
| 105 | } |
| 106 | |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 107 | /** |
| 108 | * From SecurityController |
| 109 | */ |
| 110 | @Override |
| 111 | public void onStateChanged() { |
| 112 | mHandler.post(this::updateVpn); |
| 113 | } |
| 114 | |
| 115 | @Override |
| 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); |
Luca Stefani | 4e41142 | 2018-08-31 22:05:08 +0200 | [diff] [blame] | 134 | mNetworkController.addCallback(this); |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 135 | } |
| 136 | } |
| 137 | |
| 138 | @Override |
| 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 | |
Evan Laird | af56a33 | 2018-05-23 19:52:34 -0400 | [diff] [blame] | 147 | WifiIconState newState = mWifiIconState.copy(); |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 148 | |
Evan Laird | af56a33 | 2018-05-23 19:52:34 -0400 | [diff] [blame] | 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 | |
| 164 | private void updateShowWifiSignalSpacer(WifiIconState state) { |
| 165 | MobileIconState first = getFirstMobileState(); |
| 166 | state.signalSpacerVisible = first != null && first.typeId != 0; |
| 167 | } |
| 168 | |
| 169 | private void updateWifiIconWithState(WifiIconState state) { |
| 170 | if (state.visible && state.resId > 0) { |
| 171 | mIconController.setSignalIcon(mSlotWifi, state); |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 172 | mIconController.setIconVisibility(mSlotWifi, true); |
| 173 | } else { |
| 174 | mIconController.setIconVisibility(mSlotWifi, false); |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | @Override |
| 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 | |
Evan Laird | af56a33 | 2018-05-23 19:52:34 -0400 | [diff] [blame] | 187 | // Visibility of the data type indicator changed |
| 188 | boolean typeChanged = statusType != state.typeId && (statusType == 0 || state.typeId == 0); |
| 189 | |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 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)); |
Evan Laird | af56a33 | 2018-05-23 19:52:34 -0400 | [diff] [blame] | 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 | } |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 210 | } |
| 211 | |
| 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 | |
Evan Laird | af56a33 | 2018-05-23 19:52:34 -0400 | [diff] [blame] | 222 | private MobileIconState getFirstMobileState() { |
| 223 | if (mMobileStates.size() > 0) { |
| 224 | return mMobileStates.get(0); |
| 225 | } |
| 226 | |
| 227 | return null; |
| 228 | } |
| 229 | |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 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 |
| 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 | |
| 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 |
| 264 | public void setNoSims(boolean show, boolean simDetected) { |
| 265 | // Noop yay! |
| 266 | } |
| 267 | |
| 268 | |
| 269 | @Override |
| 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 |
| 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 |
| 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 |
| 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 |
| 324 | public int hashCode() { |
| 325 | return Objects.hash(visible, activityOut, slot); |
| 326 | } |
| 327 | |
| 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 |
| 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 | |
| 357 | public void copyTo(WifiIconState other) { |
| 358 | super.copyTo(other); |
| 359 | other.resId = resId; |
| 360 | other.airplaneSpacerVisible = airplaneSpacerVisible; |
| 361 | other.signalSpacerVisible = signalSpacerVisible; |
| 362 | } |
| 363 | |
| 364 | public WifiIconState copy() { |
| 365 | WifiIconState newState = new WifiIconState(); |
| 366 | copyTo(newState); |
| 367 | return newState; |
| 368 | } |
| 369 | |
| 370 | @Override |
| 371 | public int hashCode() { |
| 372 | return Objects.hash(super.hashCode(), |
| 373 | resId, airplaneSpacerVisible, signalSpacerVisible); |
| 374 | } |
| 375 | |
| 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 | |
| 392 | private MobileIconState(int subId) { |
| 393 | super(); |
| 394 | this.subId = subId; |
| 395 | } |
| 396 | |
| 397 | @Override |
| 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 |
| 415 | public int hashCode() { |
| 416 | |
| 417 | return Objects |
| 418 | .hash(super.hashCode(), subId, strengthId, typeId, roaming, needsLeadingPadding, |
| 419 | typeContentDescription); |
| 420 | } |
| 421 | |
Evan Laird | 56fb9f8 | 2018-04-24 21:11:15 -0400 | [diff] [blame] | 422 | public MobileIconState copy() { |
| 423 | MobileIconState copy = new MobileIconState(this.subId); |
| 424 | copyTo(copy); |
| 425 | return copy; |
| 426 | } |
| 427 | |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 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 | |
| 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 | |
| 449 | @Override public String toString() { |
| 450 | return "MobileIconState(subId=" + subId + ", strengthId=" + strengthId + ", roaming=" |
Evan Laird | 6dedbe6 | 2018-04-05 16:22:36 -0400 | [diff] [blame] | 451 | + roaming + ", typeId=" + typeId + ", visible=" + visible + ")"; |
Evan Laird | e1d13c9 | 2018-03-20 16:58:01 -0400 | [diff] [blame] | 452 | } |
| 453 | } |
| 454 | } |