• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SignalClusterView(Context context)109     public SignalClusterView(Context context) {
110         this(context, null);
111     }
112 
SignalClusterView(Context context, AttributeSet attrs)113     public SignalClusterView(Context context, AttributeSet attrs) {
114         this(context, attrs, 0);
115     }
116 
SignalClusterView(Context context, AttributeSet attrs, int defStyle)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
onTuningChanged(String key, String newValue)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 
setNetworkController(NetworkControllerImpl nc)159     public void setNetworkController(NetworkControllerImpl nc) {
160         if (DEBUG) Log.d(TAG, "NetworkController=" + nc);
161         mNC = nc;
162     }
163 
setSecurityController(SecurityController sc)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
onFinishInflate()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      */
maybeScaleVpnAndNoSimsIcons()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
onAttachedToWindow()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
onDetachedFromWindow()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
onLayout(boolean changed, int l, int t, int r, int b)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
onStateChanged()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
setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon, boolean activityIn, boolean activityOut, String description)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
setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType, int qsType, boolean activityIn, boolean activityOut, String typeContentDescription, String description, boolean isWide, int subId)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
setEthernetIndicators(IconState state)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
setNoSims(boolean show)299     public void setNoSims(boolean show) {
300         mNoSimsVisible = show && !mBlockMobile;
301         apply();
302     }
303 
304     @Override
setSubs(List<SubscriptionInfo> subs)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 
hasCorrectSubs(List<SubscriptionInfo> subs)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 
getState(int subId)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 
inflatePhoneState(int subId)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
setIsAirplaneMode(IconState icon)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
setMobileDataEnabled(boolean enabled)373     public void setMobileDataEnabled(boolean enabled) {
374         // Don't care.
375     }
376 
377     @Override
dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)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
onRtlPropertiesChanged(int layoutDirection)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
hasOverlappingRendering()434     public boolean hasOverlappingRendering() {
435         return false;
436     }
437 
438     // Run after each indicator change.
apply()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      */
setIconForView(ImageView imageView, @DrawableRes int iconId)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 
setIconTint(int tint, float darkIntensity, Rect tintArea)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 
applyIconTint()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 
applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon)571     private void applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon) {
572         lightIcon.setAlpha(1 - darkIntensity);
573         darkIcon.setAlpha(darkIntensity);
574     }
575 
setTint(ImageView v, int tint)576     private void setTint(ImageView v, int tint) {
577         v.setImageTintList(ColorStateList.valueOf(tint));
578     }
579 
currentVpnIconId(boolean isBranded)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 
PhoneState(int subId, Context context)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 
setViews(ViewGroup root)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 
apply(boolean isSecondaryIcon)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 
updateAnimatableIcon(ImageView view, int resId)647         private void updateAnimatableIcon(ImageView view, int resId) {
648             maybeStopAnimatableDrawable(view);
649             setIconForView(view, resId);
650             maybeStartAnimatableDrawable(view);
651         }
652 
maybeStopAnimatableDrawable(ImageView view)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 
maybeStartAnimatableDrawable(ImageView view)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 
populateAccessibilityEvent(AccessibilityEvent event)690         public void populateAccessibilityEvent(AccessibilityEvent event) {
691             if (mMobileVisible && mMobileGroup != null
692                     && mMobileGroup.getContentDescription() != null) {
693                 event.getText().add(mMobileGroup.getContentDescription());
694             }
695         }
696 
setIconTint(int tint, float darkIntensity, Rect tintArea)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