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