Home | History | Annotate | Download | only in statusbar
      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 
    125     public SignalClusterView(Context context) {
    126         this(context, null);
    127     }
    128 
    129     public SignalClusterView(Context context, AttributeSet attrs) {
    130         this(context, attrs, 0);
    131     }
    132 
    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 
    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
    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
    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      */
    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
    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
    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
    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
    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 
    284     private void updateActivityEnabled() {
    285         mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity);
    286     }
    287 
    288     @Override
    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
    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
    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
    332     public void setNoSims(boolean show) {
    333         mNoSimsVisible = show && !mBlockMobile;
    334         apply();
    335     }
    336 
    337     @Override
    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 
    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 
    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 
    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
    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
    397     public void setMobileDataEnabled(boolean enabled) {
    398         // Don't care.
    399     }
    400 
    401     @Override
    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
    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
    449     public boolean hasOverlappingRendering() {
    450         return false;
    451     }
    452 
    453     // Run after each indicator change.
    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      */
    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 
    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      */
    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. */
    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
    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 
    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 
    631     private void applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon) {
    632         lightIcon.setAlpha(1 - darkIntensity);
    633         darkIcon.setAlpha(darkIntensity);
    634     }
    635 
    636     private void setTint(ImageView v, int tint) {
    637         v.setImageTintList(ColorStateList.valueOf(tint));
    638     }
    639 
    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 
    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 
    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 
    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 
    724         public void populateAccessibilityEvent(AccessibilityEvent event) {
    725             if (mMobileVisible && mMobileGroup != null
    726                     && mMobileGroup.getContentDescription() != null) {
    727                 event.getText().add(mMobileGroup.getContentDescription());
    728             }
    729         }
    730 
    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