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