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