1 /* 2 * Copyright (C) 2011 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; 18 19 import android.annotation.ColorInt; 20 import android.annotation.DrawableRes; 21 import android.content.Context; 22 import android.content.res.ColorStateList; 23 import android.content.res.Resources; 24 import android.content.res.TypedArray; 25 import android.graphics.Color; 26 import android.graphics.Rect; 27 import android.graphics.drawable.Animatable; 28 import android.graphics.drawable.AnimatedVectorDrawable; 29 import android.graphics.drawable.Drawable; 30 import android.graphics.drawable.LayerDrawable; 31 import android.telephony.SubscriptionInfo; 32 import android.util.ArraySet; 33 import android.util.AttributeSet; 34 import android.util.Log; 35 import android.util.TypedValue; 36 import android.view.LayoutInflater; 37 import android.view.View; 38 import android.view.ViewGroup; 39 import android.view.accessibility.AccessibilityEvent; 40 import android.widget.ImageView; 41 import android.widget.LinearLayout; 42 43 import com.android.systemui.Dependency; 44 import com.android.systemui.R; 45 import com.android.systemui.statusbar.phone.SignalDrawable; 46 import com.android.systemui.statusbar.phone.StatusBarIconController; 47 import com.android.systemui.statusbar.policy.DarkIconDispatcher; 48 import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver; 49 import com.android.systemui.statusbar.policy.NetworkController; 50 import com.android.systemui.statusbar.policy.NetworkController.IconState; 51 import com.android.systemui.statusbar.policy.NetworkControllerImpl; 52 import com.android.systemui.statusbar.policy.SecurityController; 53 import com.android.systemui.tuner.TunerService; 54 import com.android.systemui.tuner.TunerService.Tunable; 55 56 import java.util.ArrayList; 57 import java.util.List; 58 59 // Intimately tied to the design of res/layout/signal_cluster_view.xml 60 public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback, 61 SecurityController.SecurityControllerCallback, Tunable, 62 DarkReceiver { 63 64 static final String TAG = "SignalClusterView"; 65 static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 66 67 private static final String SLOT_AIRPLANE = "airplane"; 68 private static final String SLOT_MOBILE = "mobile"; 69 private static final String SLOT_WIFI = "wifi"; 70 private static final String SLOT_ETHERNET = "ethernet"; 71 72 private final NetworkController mNetworkController; 73 private final SecurityController mSecurityController; 74 75 private boolean mNoSimsVisible = false; 76 private boolean mVpnVisible = false; 77 private int mVpnIconId = 0; 78 private int mLastVpnIconId = -1; 79 private boolean mEthernetVisible = false; 80 private int mEthernetIconId = 0; 81 private int mLastEthernetIconId = -1; 82 private int mWifiBadgeId = -1; 83 private boolean mWifiVisible = false; 84 private int mWifiStrengthId = 0; 85 private int mLastWifiBadgeId = -1; 86 private int mLastWifiStrengthId = -1; 87 private boolean mWifiIn; 88 private boolean mWifiOut; 89 private int mLastWifiActivityId = -1; 90 private boolean mIsAirplaneMode = false; 91 private int mAirplaneIconId = 0; 92 private int mLastAirplaneIconId = -1; 93 private String mAirplaneContentDescription; 94 private String mWifiDescription; 95 private String mEthernetDescription; 96 private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>(); 97 private int mIconTint = Color.WHITE; 98 private float mDarkIntensity; 99 private final Rect mTintArea = new Rect(); 100 101 ViewGroup mEthernetGroup, mWifiGroup; 102 View mNoSimsCombo; 103 ImageView mVpn, mEthernet, mWifi, mAirplane, mNoSims, mEthernetDark, mWifiDark, mNoSimsDark; 104 ImageView mWifiActivityIn; 105 ImageView mWifiActivityOut; 106 View mWifiAirplaneSpacer; 107 View mWifiSignalSpacer; 108 LinearLayout mMobileSignalGroup; 109 110 private final int mMobileSignalGroupEndPadding; 111 private final int mMobileDataIconStartPadding; 112 private final int mWideTypeIconStartPadding; 113 private final int mSecondaryTelephonyPadding; 114 private final int mEndPadding; 115 private final int mEndPaddingNothingVisible; 116 private final float mIconScaleFactor; 117 118 private boolean mBlockAirplane; 119 private boolean mBlockMobile; 120 private boolean mBlockWifi; 121 private boolean mBlockEthernet; 122 private boolean mActivityEnabled; 123 private boolean mForceBlockWifi; 124 SignalClusterView(Context context)125 public SignalClusterView(Context context) { 126 this(context, null); 127 } 128 SignalClusterView(Context context, AttributeSet attrs)129 public SignalClusterView(Context context, AttributeSet attrs) { 130 this(context, attrs, 0); 131 } 132 SignalClusterView(Context context, AttributeSet attrs, int defStyle)133 public SignalClusterView(Context context, AttributeSet attrs, int defStyle) { 134 super(context, attrs, defStyle); 135 136 Resources res = getResources(); 137 mMobileSignalGroupEndPadding = 138 res.getDimensionPixelSize(R.dimen.mobile_signal_group_end_padding); 139 mMobileDataIconStartPadding = 140 res.getDimensionPixelSize(R.dimen.mobile_data_icon_start_padding); 141 mWideTypeIconStartPadding = res.getDimensionPixelSize(R.dimen.wide_type_icon_start_padding); 142 mSecondaryTelephonyPadding = res.getDimensionPixelSize(R.dimen.secondary_telephony_padding); 143 mEndPadding = res.getDimensionPixelSize(R.dimen.signal_cluster_battery_padding); 144 mEndPaddingNothingVisible = res.getDimensionPixelSize( 145 R.dimen.no_signal_cluster_battery_padding); 146 147 TypedValue typedValue = new TypedValue(); 148 res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true); 149 mIconScaleFactor = typedValue.getFloat(); 150 mNetworkController = Dependency.get(NetworkController.class); 151 mSecurityController = Dependency.get(SecurityController.class); 152 updateActivityEnabled(); 153 } 154 setForceBlockWifi()155 public void setForceBlockWifi() { 156 mForceBlockWifi = true; 157 mBlockWifi = true; 158 if (isAttachedToWindow()) { 159 // Re-register to get new callbacks. 160 mNetworkController.removeCallback(this); 161 mNetworkController.addCallback(this); 162 } 163 } 164 165 @Override onTuningChanged(String key, String newValue)166 public void onTuningChanged(String key, String newValue) { 167 if (!StatusBarIconController.ICON_BLACKLIST.equals(key)) { 168 return; 169 } 170 ArraySet<String> blockList = StatusBarIconController.getIconBlacklist(newValue); 171 boolean blockAirplane = blockList.contains(SLOT_AIRPLANE); 172 boolean blockMobile = blockList.contains(SLOT_MOBILE); 173 boolean blockWifi = blockList.contains(SLOT_WIFI); 174 boolean blockEthernet = blockList.contains(SLOT_ETHERNET); 175 176 if (blockAirplane != mBlockAirplane || blockMobile != mBlockMobile 177 || blockEthernet != mBlockEthernet || blockWifi != mBlockWifi) { 178 mBlockAirplane = blockAirplane; 179 mBlockMobile = blockMobile; 180 mBlockEthernet = blockEthernet; 181 mBlockWifi = blockWifi || mForceBlockWifi; 182 // Re-register to get new callbacks. 183 mNetworkController.removeCallback(this); 184 mNetworkController.addCallback(this); 185 } 186 } 187 188 @Override onFinishInflate()189 protected void onFinishInflate() { 190 super.onFinishInflate(); 191 192 mVpn = findViewById(R.id.vpn); 193 mEthernetGroup = findViewById(R.id.ethernet_combo); 194 mEthernet = findViewById(R.id.ethernet); 195 mEthernetDark = findViewById(R.id.ethernet_dark); 196 mWifiGroup = findViewById(R.id.wifi_combo); 197 mWifi = findViewById(R.id.wifi_signal); 198 mWifiDark = findViewById(R.id.wifi_signal_dark); 199 mWifiActivityIn = findViewById(R.id.wifi_in); 200 mWifiActivityOut= findViewById(R.id.wifi_out); 201 mAirplane = findViewById(R.id.airplane); 202 mNoSims = findViewById(R.id.no_sims); 203 mNoSimsDark = findViewById(R.id.no_sims_dark); 204 mNoSimsCombo = findViewById(R.id.no_sims_combo); 205 mWifiAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer); 206 mWifiSignalSpacer = findViewById(R.id.wifi_signal_spacer); 207 mMobileSignalGroup = findViewById(R.id.mobile_signal_group); 208 209 maybeScaleVpnAndNoSimsIcons(); 210 } 211 212 /** 213 * Extracts the icon off of the VPN and no sims views and maybe scale them by 214 * {@link #mIconScaleFactor}. Note that the other icons are not scaled here because they are 215 * dynamic. As such, they need to be scaled each time the icon changes in {@link #apply()}. 216 */ maybeScaleVpnAndNoSimsIcons()217 private void maybeScaleVpnAndNoSimsIcons() { 218 if (mIconScaleFactor == 1.f) { 219 return; 220 } 221 222 mVpn.setImageDrawable(new ScalingDrawableWrapper(mVpn.getDrawable(), mIconScaleFactor)); 223 224 mNoSims.setImageDrawable( 225 new ScalingDrawableWrapper(mNoSims.getDrawable(), mIconScaleFactor)); 226 mNoSimsDark.setImageDrawable( 227 new ScalingDrawableWrapper(mNoSimsDark.getDrawable(), mIconScaleFactor)); 228 } 229 230 @Override onAttachedToWindow()231 protected void onAttachedToWindow() { 232 super.onAttachedToWindow(); 233 mVpnVisible = mSecurityController.isVpnEnabled(); 234 mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded()); 235 236 for (PhoneState state : mPhoneStates) { 237 if (state.mMobileGroup.getParent() == null) { 238 mMobileSignalGroup.addView(state.mMobileGroup); 239 } 240 } 241 242 int endPadding = mMobileSignalGroup.getChildCount() > 0 ? mMobileSignalGroupEndPadding : 0; 243 mMobileSignalGroup.setPaddingRelative(0, 0, endPadding, 0); 244 245 Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST); 246 247 apply(); 248 applyIconTint(); 249 mNetworkController.addCallback(this); 250 mSecurityController.addCallback(this); 251 } 252 253 @Override onDetachedFromWindow()254 protected void onDetachedFromWindow() { 255 mMobileSignalGroup.removeAllViews(); 256 Dependency.get(TunerService.class).removeTunable(this); 257 mSecurityController.removeCallback(this); 258 mNetworkController.removeCallback(this); 259 260 super.onDetachedFromWindow(); 261 } 262 263 @Override onLayout(boolean changed, int l, int t, int r, int b)264 protected void onLayout(boolean changed, int l, int t, int r, int b) { 265 super.onLayout(changed, l, t, r, b); 266 267 // Re-run all checks against the tint area for all icons 268 applyIconTint(); 269 } 270 271 // From SecurityController. 272 @Override onStateChanged()273 public void onStateChanged() { 274 post(new Runnable() { 275 @Override 276 public void run() { 277 mVpnVisible = mSecurityController.isVpnEnabled(); 278 mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded()); 279 apply(); 280 } 281 }); 282 } 283 updateActivityEnabled()284 private void updateActivityEnabled() { 285 mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity); 286 } 287 288 @Override setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon, boolean activityIn, boolean activityOut, String description, boolean isTransient)289 public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon, 290 boolean activityIn, boolean activityOut, String description, boolean isTransient) { 291 mWifiVisible = statusIcon.visible && !mBlockWifi; 292 mWifiStrengthId = statusIcon.icon; 293 mWifiBadgeId = statusIcon.iconOverlay; 294 mWifiDescription = statusIcon.contentDescription; 295 mWifiIn = activityIn && mActivityEnabled && mWifiVisible; 296 mWifiOut = activityOut && mActivityEnabled && mWifiVisible; 297 298 apply(); 299 } 300 301 @Override setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType, int qsType, boolean activityIn, boolean activityOut, String typeContentDescription, String description, boolean isWide, int subId, boolean roaming)302 public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType, 303 int qsType, boolean activityIn, boolean activityOut, String typeContentDescription, 304 String description, boolean isWide, int subId, boolean roaming) { 305 PhoneState state = getState(subId); 306 if (state == null) { 307 return; 308 } 309 state.mMobileVisible = statusIcon.visible && !mBlockMobile; 310 state.mMobileStrengthId = statusIcon.icon; 311 state.mMobileTypeId = statusType; 312 state.mMobileDescription = statusIcon.contentDescription; 313 state.mMobileTypeDescription = typeContentDescription; 314 state.mIsMobileTypeIconWide = statusType != 0 && isWide; 315 state.mRoaming = roaming; 316 state.mActivityIn = activityIn && mActivityEnabled; 317 state.mActivityOut = activityOut && mActivityEnabled; 318 319 apply(); 320 } 321 322 @Override setEthernetIndicators(IconState state)323 public void setEthernetIndicators(IconState state) { 324 mEthernetVisible = state.visible && !mBlockEthernet; 325 mEthernetIconId = state.icon; 326 mEthernetDescription = state.contentDescription; 327 328 apply(); 329 } 330 331 @Override setNoSims(boolean show)332 public void setNoSims(boolean show) { 333 mNoSimsVisible = show && !mBlockMobile; 334 apply(); 335 } 336 337 @Override setSubs(List<SubscriptionInfo> subs)338 public void setSubs(List<SubscriptionInfo> subs) { 339 if (hasCorrectSubs(subs)) { 340 return; 341 } 342 mPhoneStates.clear(); 343 if (mMobileSignalGroup != null) { 344 mMobileSignalGroup.removeAllViews(); 345 } 346 final int n = subs.size(); 347 for (int i = 0; i < n; i++) { 348 inflatePhoneState(subs.get(i).getSubscriptionId()); 349 } 350 if (isAttachedToWindow()) { 351 applyIconTint(); 352 } 353 } 354 hasCorrectSubs(List<SubscriptionInfo> subs)355 private boolean hasCorrectSubs(List<SubscriptionInfo> subs) { 356 final int N = subs.size(); 357 if (N != mPhoneStates.size()) { 358 return false; 359 } 360 for (int i = 0; i < N; i++) { 361 if (mPhoneStates.get(i).mSubId != subs.get(i).getSubscriptionId()) { 362 return false; 363 } 364 } 365 return true; 366 } 367 getState(int subId)368 private PhoneState getState(int subId) { 369 for (PhoneState state : mPhoneStates) { 370 if (state.mSubId == subId) { 371 return state; 372 } 373 } 374 Log.e(TAG, "Unexpected subscription " + subId); 375 return null; 376 } 377 inflatePhoneState(int subId)378 private PhoneState inflatePhoneState(int subId) { 379 PhoneState state = new PhoneState(subId, mContext); 380 if (mMobileSignalGroup != null) { 381 mMobileSignalGroup.addView(state.mMobileGroup); 382 } 383 mPhoneStates.add(state); 384 return state; 385 } 386 387 @Override setIsAirplaneMode(IconState icon)388 public void setIsAirplaneMode(IconState icon) { 389 mIsAirplaneMode = icon.visible && !mBlockAirplane; 390 mAirplaneIconId = icon.icon; 391 mAirplaneContentDescription = icon.contentDescription; 392 393 apply(); 394 } 395 396 @Override setMobileDataEnabled(boolean enabled)397 public void setMobileDataEnabled(boolean enabled) { 398 // Don't care. 399 } 400 401 @Override dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)402 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { 403 // Standard group layout onPopulateAccessibilityEvent() implementations 404 // ignore content description, so populate manually 405 if (mEthernetVisible && mEthernetGroup != null && 406 mEthernetGroup.getContentDescription() != null) 407 event.getText().add(mEthernetGroup.getContentDescription()); 408 if (mWifiVisible && mWifiGroup != null && mWifiGroup.getContentDescription() != null) 409 event.getText().add(mWifiGroup.getContentDescription()); 410 for (PhoneState state : mPhoneStates) { 411 state.populateAccessibilityEvent(event); 412 } 413 return super.dispatchPopulateAccessibilityEventInternal(event); 414 } 415 416 @Override onRtlPropertiesChanged(int layoutDirection)417 public void onRtlPropertiesChanged(int layoutDirection) { 418 super.onRtlPropertiesChanged(layoutDirection); 419 420 if (mEthernet != null) { 421 mEthernet.setImageDrawable(null); 422 mEthernetDark.setImageDrawable(null); 423 mLastEthernetIconId = -1; 424 } 425 426 if (mWifi != null) { 427 mWifi.setImageDrawable(null); 428 mWifiDark.setImageDrawable(null); 429 mLastWifiStrengthId = -1; 430 mLastWifiBadgeId = -1; 431 } 432 433 for (PhoneState state : mPhoneStates) { 434 if (state.mMobileType != null) { 435 state.mMobileType.setImageDrawable(null); 436 state.mLastMobileTypeId = -1; 437 } 438 } 439 440 if (mAirplane != null) { 441 mAirplane.setImageDrawable(null); 442 mLastAirplaneIconId = -1; 443 } 444 445 apply(); 446 } 447 448 @Override hasOverlappingRendering()449 public boolean hasOverlappingRendering() { 450 return false; 451 } 452 453 // Run after each indicator change. apply()454 private void apply() { 455 if (mWifiGroup == null) return; 456 457 mVpn.setVisibility(mVpnVisible ? View.VISIBLE : View.GONE); 458 if (mVpnVisible) { 459 if (mLastVpnIconId != mVpnIconId) { 460 setIconForView(mVpn, mVpnIconId); 461 mLastVpnIconId = mVpnIconId; 462 } 463 mVpn.setVisibility(View.VISIBLE); 464 } else { 465 mVpn.setVisibility(View.GONE); 466 } 467 if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE")); 468 469 if (mEthernetVisible) { 470 if (mLastEthernetIconId != mEthernetIconId) { 471 setIconForView(mEthernet, mEthernetIconId); 472 setIconForView(mEthernetDark, mEthernetIconId); 473 mLastEthernetIconId = mEthernetIconId; 474 } 475 mEthernetGroup.setContentDescription(mEthernetDescription); 476 mEthernetGroup.setVisibility(View.VISIBLE); 477 } else { 478 mEthernetGroup.setVisibility(View.GONE); 479 } 480 481 if (DEBUG) Log.d(TAG, 482 String.format("ethernet: %s", 483 (mEthernetVisible ? "VISIBLE" : "GONE"))); 484 485 if (mWifiVisible) { 486 if (mWifiStrengthId != mLastWifiStrengthId || mWifiBadgeId != mLastWifiBadgeId) { 487 if (mWifiBadgeId == -1) { 488 setIconForView(mWifi, mWifiStrengthId); 489 setIconForView(mWifiDark, mWifiStrengthId); 490 } else { 491 setBadgedWifiIconForView(mWifi, mWifiStrengthId, mWifiBadgeId); 492 setBadgedWifiIconForView(mWifiDark, mWifiStrengthId, mWifiBadgeId); 493 } 494 mLastWifiStrengthId = mWifiStrengthId; 495 mLastWifiBadgeId = mWifiBadgeId; 496 } 497 mWifiGroup.setContentDescription(mWifiDescription); 498 mWifiGroup.setVisibility(View.VISIBLE); 499 } else { 500 mWifiGroup.setVisibility(View.GONE); 501 } 502 503 if (DEBUG) Log.d(TAG, 504 String.format("wifi: %s sig=%d", 505 (mWifiVisible ? "VISIBLE" : "GONE"), 506 mWifiStrengthId)); 507 508 mWifiActivityIn.setVisibility(mWifiIn ? View.VISIBLE : View.GONE); 509 mWifiActivityOut.setVisibility(mWifiOut ? View.VISIBLE : View.GONE); 510 511 boolean anyMobileVisible = false; 512 int firstMobileTypeId = 0; 513 for (PhoneState state : mPhoneStates) { 514 if (state.apply(anyMobileVisible)) { 515 if (!anyMobileVisible) { 516 firstMobileTypeId = state.mMobileTypeId; 517 anyMobileVisible = true; 518 } 519 } 520 } 521 522 if (mIsAirplaneMode) { 523 if (mLastAirplaneIconId != mAirplaneIconId) { 524 setIconForView(mAirplane, mAirplaneIconId); 525 mLastAirplaneIconId = mAirplaneIconId; 526 } 527 mAirplane.setContentDescription(mAirplaneContentDescription); 528 mAirplane.setVisibility(View.VISIBLE); 529 } else { 530 mAirplane.setVisibility(View.GONE); 531 } 532 533 if (mIsAirplaneMode && mWifiVisible) { 534 mWifiAirplaneSpacer.setVisibility(View.VISIBLE); 535 } else { 536 mWifiAirplaneSpacer.setVisibility(View.GONE); 537 } 538 539 if (((anyMobileVisible && firstMobileTypeId != 0) || mNoSimsVisible) && mWifiVisible) { 540 mWifiSignalSpacer.setVisibility(View.VISIBLE); 541 } else { 542 mWifiSignalSpacer.setVisibility(View.GONE); 543 } 544 545 mNoSimsCombo.setVisibility(mNoSimsVisible ? View.VISIBLE : View.GONE); 546 547 boolean anythingVisible = mNoSimsVisible || mWifiVisible || mIsAirplaneMode 548 || anyMobileVisible || mVpnVisible || mEthernetVisible; 549 setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0); 550 } 551 552 /** 553 * Sets the given drawable id on the view. This method will also scale the icon by 554 * {@link #mIconScaleFactor} if appropriate. 555 */ setIconForView(ImageView imageView, @DrawableRes int iconId)556 private void setIconForView(ImageView imageView, @DrawableRes int iconId) { 557 // Using the imageView's context to retrieve the Drawable so that theme is preserved. 558 Drawable icon = imageView.getContext().getDrawable(iconId); 559 560 setScaledIcon(imageView, icon); 561 } 562 setScaledIcon(ImageView imageView, Drawable icon)563 private void setScaledIcon(ImageView imageView, Drawable icon) { 564 if (mIconScaleFactor == 1.f) { 565 imageView.setImageDrawable(icon); 566 } else { 567 imageView.setImageDrawable(new ScalingDrawableWrapper(icon, mIconScaleFactor)); 568 } 569 } 570 571 /** 572 * Creates and sets a LayerDrawable from the given ids on the given view. 573 * 574 * <p>This method will also scale the icon by {@link #mIconScaleFactor} if appropriate. 575 */ setBadgedWifiIconForView(ImageView imageView, @DrawableRes int wifiPieId, @DrawableRes int badgeId)576 private void setBadgedWifiIconForView(ImageView imageView, @DrawableRes int wifiPieId, 577 @DrawableRes int badgeId) { 578 // Using the imageView's context to retrieve the Drawable so that theme is preserved.; 579 LayerDrawable icon = new LayerDrawable(new Drawable[] { 580 imageView.getContext().getDrawable(wifiPieId), 581 imageView.getContext().getDrawable(badgeId)}); 582 583 // The LayerDrawable shares an underlying state so we must mutate the object to change the 584 // color between the light and dark themes. 585 icon.mutate().setTint(getColorAttr(imageView.getContext(), R.attr.singleToneColor)); 586 587 setScaledIcon(imageView, icon); 588 } 589 590 /** Returns the given color attribute value, or white if not defined. */ getColorAttr(Context context, int attr)591 @ColorInt private static int getColorAttr(Context context, int attr) { 592 TypedArray ta = context.obtainStyledAttributes(new int[] {attr}); 593 @ColorInt int colorAccent = ta.getColor(0, Color.WHITE); 594 ta.recycle(); 595 return colorAccent; 596 } 597 598 @Override onDarkChanged(Rect tintArea, float darkIntensity, int tint)599 public void onDarkChanged(Rect tintArea, float darkIntensity, int tint) { 600 boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity 601 || !mTintArea.equals(tintArea); 602 mIconTint = tint; 603 mDarkIntensity = darkIntensity; 604 mTintArea.set(tintArea); 605 if (changed && isAttachedToWindow()) { 606 applyIconTint(); 607 } 608 } 609 applyIconTint()610 private void applyIconTint() { 611 setTint(mVpn, DarkIconDispatcher.getTint(mTintArea, mVpn, mIconTint)); 612 setTint(mAirplane, DarkIconDispatcher.getTint(mTintArea, mAirplane, mIconTint)); 613 applyDarkIntensity( 614 DarkIconDispatcher.getDarkIntensity(mTintArea, mNoSims, mDarkIntensity), 615 mNoSims, mNoSimsDark); 616 applyDarkIntensity( 617 DarkIconDispatcher.getDarkIntensity(mTintArea, mWifi, mDarkIntensity), 618 mWifi, mWifiDark); 619 setTint(mWifiActivityIn, 620 DarkIconDispatcher.getTint(mTintArea, mWifiActivityIn, mIconTint)); 621 setTint(mWifiActivityOut, 622 DarkIconDispatcher.getTint(mTintArea, mWifiActivityOut, mIconTint)); 623 applyDarkIntensity( 624 DarkIconDispatcher.getDarkIntensity(mTintArea, mEthernet, mDarkIntensity), 625 mEthernet, mEthernetDark); 626 for (int i = 0; i < mPhoneStates.size(); i++) { 627 mPhoneStates.get(i).setIconTint(mIconTint, mDarkIntensity, mTintArea); 628 } 629 } 630 applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon)631 private void applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon) { 632 lightIcon.setAlpha(1 - darkIntensity); 633 darkIcon.setAlpha(darkIntensity); 634 } 635 setTint(ImageView v, int tint)636 private void setTint(ImageView v, int tint) { 637 v.setImageTintList(ColorStateList.valueOf(tint)); 638 } 639 currentVpnIconId(boolean isBranded)640 private int currentVpnIconId(boolean isBranded) { 641 return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic; 642 } 643 644 private class PhoneState { 645 private final int mSubId; 646 private boolean mMobileVisible = false; 647 private int mMobileStrengthId = 0, mMobileTypeId = 0; 648 private int mLastMobileStrengthId = -1; 649 private int mLastMobileTypeId = -1; 650 private boolean mIsMobileTypeIconWide; 651 private String mMobileDescription, mMobileTypeDescription; 652 653 private ViewGroup mMobileGroup; 654 private ImageView mMobile, mMobileDark, mMobileType, mMobileRoaming; 655 public boolean mRoaming; 656 private ImageView mMobileActivityIn; 657 private ImageView mMobileActivityOut; 658 public boolean mActivityIn; 659 public boolean mActivityOut; 660 PhoneState(int subId, Context context)661 public PhoneState(int subId, Context context) { 662 ViewGroup root = (ViewGroup) LayoutInflater.from(context) 663 .inflate(R.layout.mobile_signal_group, null); 664 setViews(root); 665 mSubId = subId; 666 } 667 setViews(ViewGroup root)668 public void setViews(ViewGroup root) { 669 mMobileGroup = root; 670 mMobile = root.findViewById(R.id.mobile_signal); 671 mMobileDark = root.findViewById(R.id.mobile_signal_dark); 672 mMobileType = root.findViewById(R.id.mobile_type); 673 mMobileRoaming = root.findViewById(R.id.mobile_roaming); 674 mMobileActivityIn = root.findViewById(R.id.mobile_in); 675 mMobileActivityOut = root.findViewById(R.id.mobile_out); 676 // TODO: Remove the 2 instances because now the drawable can handle darkness. 677 mMobile.setImageDrawable(new SignalDrawable(mMobile.getContext())); 678 SignalDrawable drawable = new SignalDrawable(mMobileDark.getContext()); 679 drawable.setDarkIntensity(1); 680 mMobileDark.setImageDrawable(drawable); 681 } 682 apply(boolean isSecondaryIcon)683 public boolean apply(boolean isSecondaryIcon) { 684 if (mMobileVisible && !mIsAirplaneMode) { 685 if (mLastMobileStrengthId != mMobileStrengthId) { 686 mMobile.getDrawable().setLevel(mMobileStrengthId); 687 mMobileDark.getDrawable().setLevel(mMobileStrengthId); 688 mLastMobileStrengthId = mMobileStrengthId; 689 } 690 691 if (mLastMobileTypeId != mMobileTypeId) { 692 mMobileType.setImageResource(mMobileTypeId); 693 mLastMobileTypeId = mMobileTypeId; 694 } 695 696 mMobileGroup.setContentDescription(mMobileTypeDescription 697 + " " + mMobileDescription); 698 mMobileGroup.setVisibility(View.VISIBLE); 699 } else { 700 mMobileGroup.setVisibility(View.GONE); 701 } 702 703 // When this isn't next to wifi, give it some extra padding between the signals. 704 mMobileGroup.setPaddingRelative(isSecondaryIcon ? mSecondaryTelephonyPadding : 0, 705 0, 0, 0); 706 mMobile.setPaddingRelative( 707 mIsMobileTypeIconWide ? mWideTypeIconStartPadding : mMobileDataIconStartPadding, 708 0, 0, 0); 709 mMobileDark.setPaddingRelative( 710 mIsMobileTypeIconWide ? mWideTypeIconStartPadding : mMobileDataIconStartPadding, 711 0, 0, 0); 712 713 if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d", 714 (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId)); 715 716 mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE); 717 mMobileRoaming.setVisibility(mRoaming ? View.VISIBLE : View.GONE); 718 mMobileActivityIn.setVisibility(mActivityIn ? View.VISIBLE : View.GONE); 719 mMobileActivityOut.setVisibility(mActivityOut ? View.VISIBLE : View.GONE); 720 721 return mMobileVisible; 722 } 723 populateAccessibilityEvent(AccessibilityEvent event)724 public void populateAccessibilityEvent(AccessibilityEvent event) { 725 if (mMobileVisible && mMobileGroup != null 726 && mMobileGroup.getContentDescription() != null) { 727 event.getText().add(mMobileGroup.getContentDescription()); 728 } 729 } 730 setIconTint(int tint, float darkIntensity, Rect tintArea)731 public void setIconTint(int tint, float darkIntensity, Rect tintArea) { 732 applyDarkIntensity( 733 DarkIconDispatcher.getDarkIntensity(tintArea, mMobile, darkIntensity), 734 mMobile, mMobileDark); 735 setTint(mMobileType, DarkIconDispatcher.getTint(tintArea, mMobileType, tint)); 736 setTint(mMobileRoaming, DarkIconDispatcher.getTint(tintArea, mMobileRoaming, 737 tint)); 738 setTint(mMobileActivityIn, 739 DarkIconDispatcher.getTint(tintArea, mMobileActivityIn, tint)); 740 setTint(mMobileActivityOut, 741 DarkIconDispatcher.getTint(tintArea, mMobileActivityOut, tint)); 742 } 743 } 744 } 745