• 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.content.Context;
20 import android.content.res.ColorStateList;
21 import android.graphics.Color;
22 import android.graphics.PorterDuff;
23 import android.graphics.drawable.Animatable;
24 import android.graphics.drawable.Drawable;
25 import android.telephony.SubscriptionInfo;
26 import android.util.ArraySet;
27 import android.util.AttributeSet;
28 import android.util.Log;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.accessibility.AccessibilityEvent;
33 import android.widget.ImageView;
34 import android.widget.LinearLayout;
35 
36 import com.android.systemui.R;
37 import com.android.systemui.statusbar.phone.StatusBarIconController;
38 import com.android.systemui.statusbar.policy.NetworkController.IconState;
39 import com.android.systemui.statusbar.policy.NetworkControllerImpl;
40 import com.android.systemui.statusbar.policy.SecurityController;
41 import com.android.systemui.tuner.TunerService;
42 import com.android.systemui.tuner.TunerService.Tunable;
43 
44 import java.util.ArrayList;
45 import java.util.List;
46 
47 // Intimately tied to the design of res/layout/signal_cluster_view.xml
48 public class SignalClusterView
49         extends LinearLayout
50         implements NetworkControllerImpl.SignalCallback,
51         SecurityController.SecurityControllerCallback, Tunable {
52 
53     static final String TAG = "SignalClusterView";
54     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
55 
56     private static final String SLOT_AIRPLANE = "airplane";
57     private static final String SLOT_MOBILE = "mobile";
58     private static final String SLOT_WIFI = "wifi";
59     private static final String SLOT_ETHERNET = "ethernet";
60 
61     NetworkControllerImpl mNC;
62     SecurityController mSC;
63 
64     private boolean mNoSimsVisible = false;
65     private boolean mVpnVisible = false;
66     private boolean mEthernetVisible = false;
67     private int mEthernetIconId = 0;
68     private int mLastEthernetIconId = -1;
69     private boolean mWifiVisible = false;
70     private int mWifiStrengthId = 0;
71     private int mLastWifiStrengthId = -1;
72     private boolean mIsAirplaneMode = false;
73     private int mAirplaneIconId = 0;
74     private int mLastAirplaneIconId = -1;
75     private String mAirplaneContentDescription;
76     private String mWifiDescription;
77     private String mEthernetDescription;
78     private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>();
79     private int mIconTint = Color.WHITE;
80     private float mDarkIntensity;
81 
82     ViewGroup mEthernetGroup, mWifiGroup;
83     View mNoSimsCombo;
84     ImageView mVpn, mEthernet, mWifi, mAirplane, mNoSims, mEthernetDark, mWifiDark, mNoSimsDark;
85     View mWifiAirplaneSpacer;
86     View mWifiSignalSpacer;
87     LinearLayout mMobileSignalGroup;
88 
89     private int mWideTypeIconStartPadding;
90     private int mSecondaryTelephonyPadding;
91     private int mEndPadding;
92     private int mEndPaddingNothingVisible;
93 
94     private boolean mBlockAirplane;
95     private boolean mBlockMobile;
96     private boolean mBlockWifi;
97     private boolean mBlockEthernet;
98 
SignalClusterView(Context context)99     public SignalClusterView(Context context) {
100         this(context, null);
101     }
102 
SignalClusterView(Context context, AttributeSet attrs)103     public SignalClusterView(Context context, AttributeSet attrs) {
104         this(context, attrs, 0);
105     }
106 
SignalClusterView(Context context, AttributeSet attrs, int defStyle)107     public SignalClusterView(Context context, AttributeSet attrs, int defStyle) {
108         super(context, attrs, defStyle);
109     }
110 
111     @Override
onTuningChanged(String key, String newValue)112     public void onTuningChanged(String key, String newValue) {
113         if (!StatusBarIconController.ICON_BLACKLIST.equals(key)) {
114             return;
115         }
116         ArraySet<String> blockList = StatusBarIconController.getIconBlacklist(newValue);
117         boolean blockAirplane = blockList.contains(SLOT_AIRPLANE);
118         boolean blockMobile = blockList.contains(SLOT_MOBILE);
119         boolean blockWifi = blockList.contains(SLOT_WIFI);
120         boolean blockEthernet = blockList.contains(SLOT_ETHERNET);
121 
122         if (blockAirplane != mBlockAirplane || blockMobile != mBlockMobile
123                 || blockEthernet != mBlockEthernet || blockWifi != mBlockWifi) {
124             mBlockAirplane = blockAirplane;
125             mBlockMobile = blockMobile;
126             mBlockEthernet = blockEthernet;
127             mBlockWifi = blockWifi;
128             // Re-register to get new callbacks.
129             mNC.removeSignalCallback(this);
130             mNC.addSignalCallback(this);
131         }
132     }
133 
setNetworkController(NetworkControllerImpl nc)134     public void setNetworkController(NetworkControllerImpl nc) {
135         if (DEBUG) Log.d(TAG, "NetworkController=" + nc);
136         mNC = nc;
137     }
138 
setSecurityController(SecurityController sc)139     public void setSecurityController(SecurityController sc) {
140         if (DEBUG) Log.d(TAG, "SecurityController=" + sc);
141         mSC = sc;
142         mSC.addCallback(this);
143         mVpnVisible = mSC.isVpnEnabled();
144     }
145 
146     @Override
onFinishInflate()147     protected void onFinishInflate() {
148         super.onFinishInflate();
149         mWideTypeIconStartPadding = getContext().getResources().getDimensionPixelSize(
150                 R.dimen.wide_type_icon_start_padding);
151         mSecondaryTelephonyPadding = getContext().getResources().getDimensionPixelSize(
152                 R.dimen.secondary_telephony_padding);
153         mEndPadding = getContext().getResources().getDimensionPixelSize(
154                 R.dimen.signal_cluster_battery_padding);
155         mEndPaddingNothingVisible = getContext().getResources().getDimensionPixelSize(
156                 R.dimen.no_signal_cluster_battery_padding);
157     }
158 
159     @Override
onAttachedToWindow()160     protected void onAttachedToWindow() {
161         super.onAttachedToWindow();
162 
163         mVpn            = (ImageView) findViewById(R.id.vpn);
164         mEthernetGroup  = (ViewGroup) findViewById(R.id.ethernet_combo);
165         mEthernet       = (ImageView) findViewById(R.id.ethernet);
166         mEthernetDark   = (ImageView) findViewById(R.id.ethernet_dark);
167         mWifiGroup      = (ViewGroup) findViewById(R.id.wifi_combo);
168         mWifi           = (ImageView) findViewById(R.id.wifi_signal);
169         mWifiDark       = (ImageView) findViewById(R.id.wifi_signal_dark);
170         mAirplane       = (ImageView) findViewById(R.id.airplane);
171         mNoSims         = (ImageView) findViewById(R.id.no_sims);
172         mNoSimsDark     = (ImageView) findViewById(R.id.no_sims_dark);
173         mNoSimsCombo    =             findViewById(R.id.no_sims_combo);
174         mWifiAirplaneSpacer =         findViewById(R.id.wifi_airplane_spacer);
175         mWifiSignalSpacer =           findViewById(R.id.wifi_signal_spacer);
176         mMobileSignalGroup = (LinearLayout) findViewById(R.id.mobile_signal_group);
177         for (PhoneState state : mPhoneStates) {
178             mMobileSignalGroup.addView(state.mMobileGroup);
179         }
180         TunerService.get(mContext).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
181 
182         apply();
183         applyIconTint();
184     }
185 
186     @Override
onDetachedFromWindow()187     protected void onDetachedFromWindow() {
188         mVpn            = null;
189         mEthernetGroup  = null;
190         mEthernet       = null;
191         mWifiGroup      = null;
192         mWifi           = null;
193         mAirplane       = null;
194         mMobileSignalGroup.removeAllViews();
195         mMobileSignalGroup = null;
196         TunerService.get(mContext).removeTunable(this);
197 
198         super.onDetachedFromWindow();
199     }
200 
201     // From SecurityController.
202     @Override
onStateChanged()203     public void onStateChanged() {
204         post(new Runnable() {
205             @Override
206             public void run() {
207                 mVpnVisible = mSC.isVpnEnabled();
208                 apply();
209             }
210         });
211     }
212 
213     @Override
setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon, boolean activityIn, boolean activityOut, String description)214     public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
215             boolean activityIn, boolean activityOut, String description) {
216         mWifiVisible = statusIcon.visible && !mBlockWifi;
217         mWifiStrengthId = statusIcon.icon;
218         mWifiDescription = statusIcon.contentDescription;
219 
220         apply();
221     }
222 
223     @Override
setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType, int qsType, boolean activityIn, boolean activityOut, String typeContentDescription, String description, boolean isWide, int subId)224     public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
225             int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
226             String description, boolean isWide, int subId) {
227         PhoneState state = getState(subId);
228         if (state == null) {
229             return;
230         }
231         state.mMobileVisible = statusIcon.visible && !mBlockMobile;
232         state.mMobileStrengthId = statusIcon.icon;
233         state.mMobileTypeId = statusType;
234         state.mMobileDescription = statusIcon.contentDescription;
235         state.mMobileTypeDescription = typeContentDescription;
236         state.mIsMobileTypeIconWide = statusType != 0 && isWide;
237 
238         apply();
239     }
240 
241     @Override
setEthernetIndicators(IconState state)242     public void setEthernetIndicators(IconState state) {
243         mEthernetVisible = state.visible && !mBlockEthernet;
244         mEthernetIconId = state.icon;
245         mEthernetDescription = state.contentDescription;
246 
247         apply();
248     }
249 
250     @Override
setNoSims(boolean show)251     public void setNoSims(boolean show) {
252         mNoSimsVisible = show && !mBlockMobile;
253         apply();
254     }
255 
256     @Override
setSubs(List<SubscriptionInfo> subs)257     public void setSubs(List<SubscriptionInfo> subs) {
258         if (hasCorrectSubs(subs)) {
259             return;
260         }
261         // Clear out all old subIds.
262         mPhoneStates.clear();
263         if (mMobileSignalGroup != null) {
264             mMobileSignalGroup.removeAllViews();
265         }
266         final int n = subs.size();
267         for (int i = 0; i < n; i++) {
268             inflatePhoneState(subs.get(i).getSubscriptionId());
269         }
270         if (isAttachedToWindow()) {
271             applyIconTint();
272         }
273     }
274 
hasCorrectSubs(List<SubscriptionInfo> subs)275     private boolean hasCorrectSubs(List<SubscriptionInfo> subs) {
276         final int N = subs.size();
277         if (N != mPhoneStates.size()) {
278             return false;
279         }
280         for (int i = 0; i < N; i++) {
281             if (mPhoneStates.get(i).mSubId != subs.get(i).getSubscriptionId()) {
282                 return false;
283             }
284         }
285         return true;
286     }
287 
getState(int subId)288     private PhoneState getState(int subId) {
289         for (PhoneState state : mPhoneStates) {
290             if (state.mSubId == subId) {
291                 return state;
292             }
293         }
294         Log.e(TAG, "Unexpected subscription " + subId);
295         return null;
296     }
297 
inflatePhoneState(int subId)298     private PhoneState inflatePhoneState(int subId) {
299         PhoneState state = new PhoneState(subId, mContext);
300         if (mMobileSignalGroup != null) {
301             mMobileSignalGroup.addView(state.mMobileGroup);
302         }
303         mPhoneStates.add(state);
304         return state;
305     }
306 
307     @Override
setIsAirplaneMode(IconState icon)308     public void setIsAirplaneMode(IconState icon) {
309         mIsAirplaneMode = icon.visible && !mBlockAirplane;
310         mAirplaneIconId = icon.icon;
311         mAirplaneContentDescription = icon.contentDescription;
312 
313         apply();
314     }
315 
316     @Override
setMobileDataEnabled(boolean enabled)317     public void setMobileDataEnabled(boolean enabled) {
318         // Don't care.
319     }
320 
321     @Override
dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)322     public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
323         // Standard group layout onPopulateAccessibilityEvent() implementations
324         // ignore content description, so populate manually
325         if (mEthernetVisible && mEthernetGroup != null &&
326                 mEthernetGroup.getContentDescription() != null)
327             event.getText().add(mEthernetGroup.getContentDescription());
328         if (mWifiVisible && mWifiGroup != null && mWifiGroup.getContentDescription() != null)
329             event.getText().add(mWifiGroup.getContentDescription());
330         for (PhoneState state : mPhoneStates) {
331             state.populateAccessibilityEvent(event);
332         }
333         return super.dispatchPopulateAccessibilityEventInternal(event);
334     }
335 
336     @Override
onRtlPropertiesChanged(int layoutDirection)337     public void onRtlPropertiesChanged(int layoutDirection) {
338         super.onRtlPropertiesChanged(layoutDirection);
339 
340         if (mEthernet != null) {
341             mEthernet.setImageDrawable(null);
342             mEthernetDark.setImageDrawable(null);
343             mLastEthernetIconId = -1;
344         }
345 
346         if (mWifi != null) {
347             mWifi.setImageDrawable(null);
348             mWifiDark.setImageDrawable(null);
349             mLastWifiStrengthId = -1;
350         }
351 
352         for (PhoneState state : mPhoneStates) {
353             if (state.mMobile != null) {
354                 state.mMobile.setImageDrawable(null);
355             }
356             if (state.mMobileType != null) {
357                 state.mMobileType.setImageDrawable(null);
358             }
359         }
360 
361         if (mAirplane != null) {
362             mAirplane.setImageDrawable(null);
363             mLastAirplaneIconId = -1;
364         }
365 
366         apply();
367     }
368 
369     @Override
hasOverlappingRendering()370     public boolean hasOverlappingRendering() {
371         return false;
372     }
373 
374     // Run after each indicator change.
apply()375     private void apply() {
376         if (mWifiGroup == null) return;
377 
378         mVpn.setVisibility(mVpnVisible ? View.VISIBLE : View.GONE);
379         if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE"));
380 
381         if (mEthernetVisible) {
382             if (mLastEthernetIconId != mEthernetIconId) {
383                 mEthernet.setImageResource(mEthernetIconId);
384                 mEthernetDark.setImageResource(mEthernetIconId);
385                 mLastEthernetIconId = mEthernetIconId;
386             }
387             mEthernetGroup.setContentDescription(mEthernetDescription);
388             mEthernetGroup.setVisibility(View.VISIBLE);
389         } else {
390             mEthernetGroup.setVisibility(View.GONE);
391         }
392 
393         if (DEBUG) Log.d(TAG,
394                 String.format("ethernet: %s",
395                     (mEthernetVisible ? "VISIBLE" : "GONE")));
396 
397 
398         if (mWifiVisible) {
399             if (mWifiStrengthId != mLastWifiStrengthId) {
400                 mWifi.setImageResource(mWifiStrengthId);
401                 mWifiDark.setImageResource(mWifiStrengthId);
402                 mLastWifiStrengthId = mWifiStrengthId;
403             }
404             mWifiGroup.setContentDescription(mWifiDescription);
405             mWifiGroup.setVisibility(View.VISIBLE);
406         } else {
407             mWifiGroup.setVisibility(View.GONE);
408         }
409 
410         if (DEBUG) Log.d(TAG,
411                 String.format("wifi: %s sig=%d",
412                     (mWifiVisible ? "VISIBLE" : "GONE"),
413                     mWifiStrengthId));
414 
415         boolean anyMobileVisible = false;
416         int firstMobileTypeId = 0;
417         for (PhoneState state : mPhoneStates) {
418             if (state.apply(anyMobileVisible)) {
419                 if (!anyMobileVisible) {
420                     firstMobileTypeId = state.mMobileTypeId;
421                     anyMobileVisible = true;
422                 }
423             }
424         }
425 
426         if (mIsAirplaneMode) {
427             if (mLastAirplaneIconId != mAirplaneIconId) {
428                 mAirplane.setImageResource(mAirplaneIconId);
429                 mLastAirplaneIconId = mAirplaneIconId;
430             }
431             mAirplane.setContentDescription(mAirplaneContentDescription);
432             mAirplane.setVisibility(View.VISIBLE);
433         } else {
434             mAirplane.setVisibility(View.GONE);
435         }
436 
437         if (mIsAirplaneMode && mWifiVisible) {
438             mWifiAirplaneSpacer.setVisibility(View.VISIBLE);
439         } else {
440             mWifiAirplaneSpacer.setVisibility(View.GONE);
441         }
442 
443         if (((anyMobileVisible && firstMobileTypeId != 0) || mNoSimsVisible) && mWifiVisible) {
444             mWifiSignalSpacer.setVisibility(View.VISIBLE);
445         } else {
446             mWifiSignalSpacer.setVisibility(View.GONE);
447         }
448 
449         mNoSimsCombo.setVisibility(mNoSimsVisible ? View.VISIBLE : View.GONE);
450 
451         boolean anythingVisible = mNoSimsVisible || mWifiVisible || mIsAirplaneMode
452                 || anyMobileVisible || mVpnVisible || mEthernetVisible;
453         setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0);
454     }
455 
setIconTint(int tint, float darkIntensity)456     public void setIconTint(int tint, float darkIntensity) {
457         boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity;
458         mIconTint = tint;
459         mDarkIntensity = darkIntensity;
460         if (changed && isAttachedToWindow()) {
461             applyIconTint();
462         }
463     }
464 
applyIconTint()465     private void applyIconTint() {
466         setTint(mVpn, mIconTint);
467         setTint(mAirplane, mIconTint);
468         applyDarkIntensity(mDarkIntensity, mNoSims, mNoSimsDark);
469         applyDarkIntensity(mDarkIntensity, mWifi, mWifiDark);
470         applyDarkIntensity(mDarkIntensity, mEthernet, mEthernetDark);
471         for (int i = 0; i < mPhoneStates.size(); i++) {
472             mPhoneStates.get(i).setIconTint(mIconTint, mDarkIntensity);
473         }
474     }
475 
applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon)476     private void applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon) {
477         lightIcon.setAlpha(1 - darkIntensity);
478         darkIcon.setAlpha(darkIntensity);
479     }
480 
setTint(ImageView v, int tint)481     private void setTint(ImageView v, int tint) {
482         v.setImageTintList(ColorStateList.valueOf(tint));
483     }
484 
485     private class PhoneState {
486         private final int mSubId;
487         private boolean mMobileVisible = false;
488         private int mMobileStrengthId = 0, mMobileTypeId = 0;
489         private boolean mIsMobileTypeIconWide;
490         private String mMobileDescription, mMobileTypeDescription;
491 
492         private ViewGroup mMobileGroup;
493         private ImageView mMobile, mMobileDark, mMobileType;
494 
PhoneState(int subId, Context context)495         public PhoneState(int subId, Context context) {
496             ViewGroup root = (ViewGroup) LayoutInflater.from(context)
497                     .inflate(R.layout.mobile_signal_group, null);
498             setViews(root);
499             mSubId = subId;
500         }
501 
setViews(ViewGroup root)502         public void setViews(ViewGroup root) {
503             mMobileGroup    = root;
504             mMobile         = (ImageView) root.findViewById(R.id.mobile_signal);
505             mMobileDark     = (ImageView) root.findViewById(R.id.mobile_signal_dark);
506             mMobileType     = (ImageView) root.findViewById(R.id.mobile_type);
507         }
508 
apply(boolean isSecondaryIcon)509         public boolean apply(boolean isSecondaryIcon) {
510             if (mMobileVisible && !mIsAirplaneMode) {
511                 mMobile.setImageResource(mMobileStrengthId);
512                 Drawable mobileDrawable = mMobile.getDrawable();
513                 if (mobileDrawable instanceof Animatable) {
514                     Animatable ad = (Animatable) mobileDrawable;
515                     if (!ad.isRunning()) {
516                         ad.start();
517                     }
518                 }
519 
520                 mMobileDark.setImageResource(mMobileStrengthId);
521                 Drawable mobileDarkDrawable = mMobileDark.getDrawable();
522                 if (mobileDarkDrawable instanceof Animatable) {
523                     Animatable ad = (Animatable) mobileDarkDrawable;
524                     if (!ad.isRunning()) {
525                         ad.start();
526                     }
527                 }
528 
529                 mMobileType.setImageResource(mMobileTypeId);
530                 mMobileGroup.setContentDescription(mMobileTypeDescription
531                         + " " + mMobileDescription);
532                 mMobileGroup.setVisibility(View.VISIBLE);
533             } else {
534                 mMobileGroup.setVisibility(View.GONE);
535             }
536 
537             // When this isn't next to wifi, give it some extra padding between the signals.
538             mMobileGroup.setPaddingRelative(isSecondaryIcon ? mSecondaryTelephonyPadding : 0,
539                     0, 0, 0);
540             mMobile.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0,
541                     0, 0, 0);
542             mMobileDark.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0,
543                     0, 0, 0);
544 
545             if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d",
546                         (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId));
547 
548             mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE);
549 
550             return mMobileVisible;
551         }
552 
populateAccessibilityEvent(AccessibilityEvent event)553         public void populateAccessibilityEvent(AccessibilityEvent event) {
554             if (mMobileVisible && mMobileGroup != null
555                     && mMobileGroup.getContentDescription() != null) {
556                 event.getText().add(mMobileGroup.getContentDescription());
557             }
558         }
559 
setIconTint(int tint, float darkIntensity)560         public void setIconTint(int tint, float darkIntensity) {
561             applyDarkIntensity(darkIntensity, mMobile, mMobileDark);
562             setTint(mMobileType, tint);
563         }
564     }
565 }
566 
567