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 } 254 255 @Override setSubs(List<SubscriptionInfo> subs)256 public void setSubs(List<SubscriptionInfo> subs) { 257 if (hasCorrectSubs(subs)) { 258 return; 259 } 260 // Clear out all old subIds. 261 mPhoneStates.clear(); 262 if (mMobileSignalGroup != null) { 263 mMobileSignalGroup.removeAllViews(); 264 } 265 final int n = subs.size(); 266 for (int i = 0; i < n; i++) { 267 inflatePhoneState(subs.get(i).getSubscriptionId()); 268 } 269 if (isAttachedToWindow()) { 270 applyIconTint(); 271 } 272 } 273 hasCorrectSubs(List<SubscriptionInfo> subs)274 private boolean hasCorrectSubs(List<SubscriptionInfo> subs) { 275 final int N = subs.size(); 276 if (N != mPhoneStates.size()) { 277 return false; 278 } 279 for (int i = 0; i < N; i++) { 280 if (mPhoneStates.get(i).mSubId != subs.get(i).getSubscriptionId()) { 281 return false; 282 } 283 } 284 return true; 285 } 286 getState(int subId)287 private PhoneState getState(int subId) { 288 for (PhoneState state : mPhoneStates) { 289 if (state.mSubId == subId) { 290 return state; 291 } 292 } 293 Log.e(TAG, "Unexpected subscription " + subId); 294 return null; 295 } 296 inflatePhoneState(int subId)297 private PhoneState inflatePhoneState(int subId) { 298 PhoneState state = new PhoneState(subId, mContext); 299 if (mMobileSignalGroup != null) { 300 mMobileSignalGroup.addView(state.mMobileGroup); 301 } 302 mPhoneStates.add(state); 303 return state; 304 } 305 306 @Override setIsAirplaneMode(IconState icon)307 public void setIsAirplaneMode(IconState icon) { 308 mIsAirplaneMode = icon.visible && !mBlockAirplane; 309 mAirplaneIconId = icon.icon; 310 mAirplaneContentDescription = icon.contentDescription; 311 312 apply(); 313 } 314 315 @Override setMobileDataEnabled(boolean enabled)316 public void setMobileDataEnabled(boolean enabled) { 317 // Don't care. 318 } 319 320 @Override dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)321 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { 322 // Standard group layout onPopulateAccessibilityEvent() implementations 323 // ignore content description, so populate manually 324 if (mEthernetVisible && mEthernetGroup != null && 325 mEthernetGroup.getContentDescription() != null) 326 event.getText().add(mEthernetGroup.getContentDescription()); 327 if (mWifiVisible && mWifiGroup != null && mWifiGroup.getContentDescription() != null) 328 event.getText().add(mWifiGroup.getContentDescription()); 329 for (PhoneState state : mPhoneStates) { 330 state.populateAccessibilityEvent(event); 331 } 332 return super.dispatchPopulateAccessibilityEventInternal(event); 333 } 334 335 @Override onRtlPropertiesChanged(int layoutDirection)336 public void onRtlPropertiesChanged(int layoutDirection) { 337 super.onRtlPropertiesChanged(layoutDirection); 338 339 if (mEthernet != null) { 340 mEthernet.setImageDrawable(null); 341 mEthernetDark.setImageDrawable(null); 342 mLastEthernetIconId = -1; 343 } 344 345 if (mWifi != null) { 346 mWifi.setImageDrawable(null); 347 mWifiDark.setImageDrawable(null); 348 mLastWifiStrengthId = -1; 349 } 350 351 for (PhoneState state : mPhoneStates) { 352 if (state.mMobile != null) { 353 state.mMobile.setImageDrawable(null); 354 } 355 if (state.mMobileType != null) { 356 state.mMobileType.setImageDrawable(null); 357 } 358 } 359 360 if (mAirplane != null) { 361 mAirplane.setImageDrawable(null); 362 mLastAirplaneIconId = -1; 363 } 364 365 apply(); 366 } 367 368 @Override hasOverlappingRendering()369 public boolean hasOverlappingRendering() { 370 return false; 371 } 372 373 // Run after each indicator change. apply()374 private void apply() { 375 if (mWifiGroup == null) return; 376 377 mVpn.setVisibility(mVpnVisible ? View.VISIBLE : View.GONE); 378 if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE")); 379 380 if (mEthernetVisible) { 381 if (mLastEthernetIconId != mEthernetIconId) { 382 mEthernet.setImageResource(mEthernetIconId); 383 mEthernetDark.setImageResource(mEthernetIconId); 384 mLastEthernetIconId = mEthernetIconId; 385 } 386 mEthernetGroup.setContentDescription(mEthernetDescription); 387 mEthernetGroup.setVisibility(View.VISIBLE); 388 } else { 389 mEthernetGroup.setVisibility(View.GONE); 390 } 391 392 if (DEBUG) Log.d(TAG, 393 String.format("ethernet: %s", 394 (mEthernetVisible ? "VISIBLE" : "GONE"))); 395 396 397 if (mWifiVisible) { 398 if (mWifiStrengthId != mLastWifiStrengthId) { 399 mWifi.setImageResource(mWifiStrengthId); 400 mWifiDark.setImageResource(mWifiStrengthId); 401 mLastWifiStrengthId = mWifiStrengthId; 402 } 403 mWifiGroup.setContentDescription(mWifiDescription); 404 mWifiGroup.setVisibility(View.VISIBLE); 405 } else { 406 mWifiGroup.setVisibility(View.GONE); 407 } 408 409 if (DEBUG) Log.d(TAG, 410 String.format("wifi: %s sig=%d", 411 (mWifiVisible ? "VISIBLE" : "GONE"), 412 mWifiStrengthId)); 413 414 boolean anyMobileVisible = false; 415 int firstMobileTypeId = 0; 416 for (PhoneState state : mPhoneStates) { 417 if (state.apply(anyMobileVisible)) { 418 if (!anyMobileVisible) { 419 firstMobileTypeId = state.mMobileTypeId; 420 anyMobileVisible = true; 421 } 422 } 423 } 424 425 if (mIsAirplaneMode) { 426 if (mLastAirplaneIconId != mAirplaneIconId) { 427 mAirplane.setImageResource(mAirplaneIconId); 428 mLastAirplaneIconId = mAirplaneIconId; 429 } 430 mAirplane.setContentDescription(mAirplaneContentDescription); 431 mAirplane.setVisibility(View.VISIBLE); 432 } else { 433 mAirplane.setVisibility(View.GONE); 434 } 435 436 if (mIsAirplaneMode && mWifiVisible) { 437 mWifiAirplaneSpacer.setVisibility(View.VISIBLE); 438 } else { 439 mWifiAirplaneSpacer.setVisibility(View.GONE); 440 } 441 442 if (((anyMobileVisible && firstMobileTypeId != 0) || mNoSimsVisible) && mWifiVisible) { 443 mWifiSignalSpacer.setVisibility(View.VISIBLE); 444 } else { 445 mWifiSignalSpacer.setVisibility(View.GONE); 446 } 447 448 mNoSimsCombo.setVisibility(mNoSimsVisible ? View.VISIBLE : View.GONE); 449 450 boolean anythingVisible = mNoSimsVisible || mWifiVisible || mIsAirplaneMode 451 || anyMobileVisible || mVpnVisible || mEthernetVisible; 452 setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0); 453 } 454 setIconTint(int tint, float darkIntensity)455 public void setIconTint(int tint, float darkIntensity) { 456 boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity; 457 mIconTint = tint; 458 mDarkIntensity = darkIntensity; 459 if (changed && isAttachedToWindow()) { 460 applyIconTint(); 461 } 462 } 463 applyIconTint()464 private void applyIconTint() { 465 setTint(mVpn, mIconTint); 466 setTint(mAirplane, mIconTint); 467 applyDarkIntensity(mDarkIntensity, mNoSims, mNoSimsDark); 468 applyDarkIntensity(mDarkIntensity, mWifi, mWifiDark); 469 applyDarkIntensity(mDarkIntensity, mEthernet, mEthernetDark); 470 for (int i = 0; i < mPhoneStates.size(); i++) { 471 mPhoneStates.get(i).setIconTint(mIconTint, mDarkIntensity); 472 } 473 } 474 applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon)475 private void applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon) { 476 lightIcon.setAlpha(1 - darkIntensity); 477 darkIcon.setAlpha(darkIntensity); 478 } 479 setTint(ImageView v, int tint)480 private void setTint(ImageView v, int tint) { 481 v.setImageTintList(ColorStateList.valueOf(tint)); 482 } 483 484 private class PhoneState { 485 private final int mSubId; 486 private boolean mMobileVisible = false; 487 private int mMobileStrengthId = 0, mMobileTypeId = 0; 488 private boolean mIsMobileTypeIconWide; 489 private String mMobileDescription, mMobileTypeDescription; 490 491 private ViewGroup mMobileGroup; 492 private ImageView mMobile, mMobileDark, mMobileType; 493 PhoneState(int subId, Context context)494 public PhoneState(int subId, Context context) { 495 ViewGroup root = (ViewGroup) LayoutInflater.from(context) 496 .inflate(R.layout.mobile_signal_group, null); 497 setViews(root); 498 mSubId = subId; 499 } 500 setViews(ViewGroup root)501 public void setViews(ViewGroup root) { 502 mMobileGroup = root; 503 mMobile = (ImageView) root.findViewById(R.id.mobile_signal); 504 mMobileDark = (ImageView) root.findViewById(R.id.mobile_signal_dark); 505 mMobileType = (ImageView) root.findViewById(R.id.mobile_type); 506 } 507 apply(boolean isSecondaryIcon)508 public boolean apply(boolean isSecondaryIcon) { 509 if (mMobileVisible && !mIsAirplaneMode) { 510 mMobile.setImageResource(mMobileStrengthId); 511 Drawable mobileDrawable = mMobile.getDrawable(); 512 if (mobileDrawable instanceof Animatable) { 513 Animatable ad = (Animatable) mobileDrawable; 514 if (!ad.isRunning()) { 515 ad.start(); 516 } 517 } 518 519 mMobileDark.setImageResource(mMobileStrengthId); 520 Drawable mobileDarkDrawable = mMobileDark.getDrawable(); 521 if (mobileDarkDrawable instanceof Animatable) { 522 Animatable ad = (Animatable) mobileDarkDrawable; 523 if (!ad.isRunning()) { 524 ad.start(); 525 } 526 } 527 528 mMobileType.setImageResource(mMobileTypeId); 529 mMobileGroup.setContentDescription(mMobileTypeDescription 530 + " " + mMobileDescription); 531 mMobileGroup.setVisibility(View.VISIBLE); 532 } else { 533 mMobileGroup.setVisibility(View.GONE); 534 } 535 536 // When this isn't next to wifi, give it some extra padding between the signals. 537 mMobileGroup.setPaddingRelative(isSecondaryIcon ? mSecondaryTelephonyPadding : 0, 538 0, 0, 0); 539 mMobile.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0, 540 0, 0, 0); 541 mMobileDark.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0, 542 0, 0, 0); 543 544 if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d", 545 (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId)); 546 547 mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE); 548 549 return mMobileVisible; 550 } 551 populateAccessibilityEvent(AccessibilityEvent event)552 public void populateAccessibilityEvent(AccessibilityEvent event) { 553 if (mMobileVisible && mMobileGroup != null 554 && mMobileGroup.getContentDescription() != null) { 555 event.getText().add(mMobileGroup.getContentDescription()); 556 } 557 } 558 setIconTint(int tint, float darkIntensity)559 public void setIconTint(int tint, float darkIntensity) { 560 applyDarkIntensity(darkIntensity, mMobile, mMobileDark); 561 setTint(mMobileType, tint); 562 } 563 } 564 } 565 566