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