1 /* 2 * Copyright (C) 2015 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.phone; 18 19 import android.animation.ArgbEvaluator; 20 import android.animation.ValueAnimator; 21 import android.content.Context; 22 import android.content.res.ColorStateList; 23 import android.graphics.Color; 24 import android.os.Bundle; 25 import android.os.Handler; 26 import android.os.SystemClock; 27 import android.text.TextUtils; 28 import android.util.ArraySet; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.view.animation.AnimationUtils; 32 import android.view.animation.Interpolator; 33 import android.widget.ImageView; 34 import android.widget.LinearLayout; 35 import android.widget.TextView; 36 37 import com.android.internal.statusbar.StatusBarIcon; 38 import com.android.internal.util.NotificationColorUtil; 39 import com.android.systemui.BatteryMeterView; 40 import com.android.systemui.FontSizeUtils; 41 import com.android.systemui.R; 42 import com.android.systemui.statusbar.NotificationData; 43 import com.android.systemui.statusbar.SignalClusterView; 44 import com.android.systemui.statusbar.StatusBarIconView; 45 import com.android.systemui.tuner.TunerService; 46 import com.android.systemui.tuner.TunerService.Tunable; 47 48 import java.io.PrintWriter; 49 import java.util.ArrayList; 50 51 /** 52 * Controls everything regarding the icons in the status bar and on Keyguard, including, but not 53 * limited to: notification icons, signal cluster, additional status icons, and clock in the status 54 * bar. 55 */ 56 public class StatusBarIconController implements Tunable { 57 58 public static final long DEFAULT_TINT_ANIMATION_DURATION = 120; 59 60 public static final String ICON_BLACKLIST = "icon_blacklist"; 61 62 private Context mContext; 63 private PhoneStatusBar mPhoneStatusBar; 64 private Interpolator mLinearOutSlowIn; 65 private Interpolator mFastOutSlowIn; 66 private DemoStatusIcons mDemoStatusIcons; 67 private NotificationColorUtil mNotificationColorUtil; 68 69 private LinearLayout mSystemIconArea; 70 private LinearLayout mStatusIcons; 71 private SignalClusterView mSignalCluster; 72 private LinearLayout mStatusIconsKeyguard; 73 private IconMerger mNotificationIcons; 74 private View mNotificationIconArea; 75 private ImageView mMoreIcon; 76 private BatteryMeterView mBatteryMeterView; 77 private TextView mClock; 78 79 private int mIconSize; 80 private int mIconHPadding; 81 82 private int mIconTint = Color.WHITE; 83 private float mDarkIntensity; 84 85 private boolean mTransitionPending; 86 private boolean mTintChangePending; 87 private float mPendingDarkIntensity; 88 private ValueAnimator mTintAnimator; 89 90 private int mDarkModeIconColorSingleTone; 91 private int mLightModeIconColorSingleTone; 92 93 private final Handler mHandler; 94 private boolean mTransitionDeferring; 95 private long mTransitionDeferringStartTime; 96 private long mTransitionDeferringDuration; 97 98 private final ArraySet<String> mIconBlacklist = new ArraySet<>(); 99 100 private final Runnable mTransitionDeferringDoneRunnable = new Runnable() { 101 @Override 102 public void run() { 103 mTransitionDeferring = false; 104 } 105 }; 106 StatusBarIconController(Context context, View statusBar, View keyguardStatusBar, PhoneStatusBar phoneStatusBar)107 public StatusBarIconController(Context context, View statusBar, View keyguardStatusBar, 108 PhoneStatusBar phoneStatusBar) { 109 mContext = context; 110 mPhoneStatusBar = phoneStatusBar; 111 mNotificationColorUtil = NotificationColorUtil.getInstance(context); 112 mSystemIconArea = (LinearLayout) statusBar.findViewById(R.id.system_icon_area); 113 mStatusIcons = (LinearLayout) statusBar.findViewById(R.id.statusIcons); 114 mSignalCluster = (SignalClusterView) statusBar.findViewById(R.id.signal_cluster); 115 mNotificationIconArea = statusBar.findViewById(R.id.notification_icon_area_inner); 116 mNotificationIcons = (IconMerger) statusBar.findViewById(R.id.notificationIcons); 117 mMoreIcon = (ImageView) statusBar.findViewById(R.id.moreIcon); 118 mNotificationIcons.setOverflowIndicator(mMoreIcon); 119 mStatusIconsKeyguard = (LinearLayout) keyguardStatusBar.findViewById(R.id.statusIcons); 120 mBatteryMeterView = (BatteryMeterView) statusBar.findViewById(R.id.battery); 121 mClock = (TextView) statusBar.findViewById(R.id.clock); 122 mLinearOutSlowIn = AnimationUtils.loadInterpolator(mContext, 123 android.R.interpolator.linear_out_slow_in); 124 mFastOutSlowIn = AnimationUtils.loadInterpolator(mContext, 125 android.R.interpolator.fast_out_slow_in); 126 mDarkModeIconColorSingleTone = context.getColor(R.color.dark_mode_icon_color_single_tone); 127 mLightModeIconColorSingleTone = context.getColor(R.color.light_mode_icon_color_single_tone); 128 mHandler = new Handler(); 129 updateResources(); 130 131 TunerService.get(mContext).addTunable(this, ICON_BLACKLIST); 132 } 133 134 @Override onTuningChanged(String key, String newValue)135 public void onTuningChanged(String key, String newValue) { 136 if (!ICON_BLACKLIST.equals(key)) { 137 return; 138 } 139 mIconBlacklist.clear(); 140 mIconBlacklist.addAll(getIconBlacklist(newValue)); 141 ArrayList<StatusBarIconView> views = new ArrayList<StatusBarIconView>(); 142 // Get all the current views. 143 for (int i = 0; i < mStatusIcons.getChildCount(); i++) { 144 views.add((StatusBarIconView) mStatusIcons.getChildAt(i)); 145 } 146 // Remove all the icons. 147 for (int i = views.size() - 1; i >= 0; i--) { 148 removeSystemIcon(views.get(i).getSlot(), i, i); 149 } 150 // Add them all back 151 for (int i = 0; i < views.size(); i++) { 152 addSystemIcon(views.get(i).getSlot(), i, i, views.get(i).getStatusBarIcon()); 153 } 154 }; 155 updateResources()156 public void updateResources() { 157 mIconSize = mContext.getResources().getDimensionPixelSize( 158 com.android.internal.R.dimen.status_bar_icon_size); 159 mIconHPadding = mContext.getResources().getDimensionPixelSize( 160 R.dimen.status_bar_icon_padding); 161 FontSizeUtils.updateFontSize(mClock, R.dimen.status_bar_clock_size); 162 } 163 addSystemIcon(String slot, int index, int viewIndex, StatusBarIcon icon)164 public void addSystemIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { 165 boolean blocked = mIconBlacklist.contains(slot); 166 StatusBarIconView view = new StatusBarIconView(mContext, slot, null, blocked); 167 view.set(icon); 168 mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams( 169 ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize)); 170 view = new StatusBarIconView(mContext, slot, null, blocked); 171 view.set(icon); 172 mStatusIconsKeyguard.addView(view, viewIndex, new LinearLayout.LayoutParams( 173 ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize)); 174 applyIconTint(); 175 } 176 updateSystemIcon(String slot, int index, int viewIndex, StatusBarIcon old, StatusBarIcon icon)177 public void updateSystemIcon(String slot, int index, int viewIndex, 178 StatusBarIcon old, StatusBarIcon icon) { 179 StatusBarIconView view = (StatusBarIconView) mStatusIcons.getChildAt(viewIndex); 180 view.set(icon); 181 view = (StatusBarIconView) mStatusIconsKeyguard.getChildAt(viewIndex); 182 view.set(icon); 183 applyIconTint(); 184 } 185 removeSystemIcon(String slot, int index, int viewIndex)186 public void removeSystemIcon(String slot, int index, int viewIndex) { 187 mStatusIcons.removeViewAt(viewIndex); 188 mStatusIconsKeyguard.removeViewAt(viewIndex); 189 } 190 updateNotificationIcons(NotificationData notificationData)191 public void updateNotificationIcons(NotificationData notificationData) { 192 final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( 193 mIconSize + 2*mIconHPadding, mPhoneStatusBar.getStatusBarHeight()); 194 195 ArrayList<NotificationData.Entry> activeNotifications = 196 notificationData.getActiveNotifications(); 197 final int N = activeNotifications.size(); 198 ArrayList<StatusBarIconView> toShow = new ArrayList<>(N); 199 200 // Filter out ambient notifications and notification children. 201 for (int i = 0; i < N; i++) { 202 NotificationData.Entry ent = activeNotifications.get(i); 203 if (notificationData.isAmbient(ent.key) 204 && !NotificationData.showNotificationEvenIfUnprovisioned(ent.notification)) { 205 continue; 206 } 207 if (!PhoneStatusBar.isTopLevelChild(ent)) { 208 continue; 209 } 210 toShow.add(ent.icon); 211 } 212 213 ArrayList<View> toRemove = new ArrayList<>(); 214 for (int i=0; i<mNotificationIcons.getChildCount(); i++) { 215 View child = mNotificationIcons.getChildAt(i); 216 if (!toShow.contains(child)) { 217 toRemove.add(child); 218 } 219 } 220 221 final int toRemoveCount = toRemove.size(); 222 for (int i = 0; i < toRemoveCount; i++) { 223 mNotificationIcons.removeView(toRemove.get(i)); 224 } 225 226 for (int i=0; i<toShow.size(); i++) { 227 View v = toShow.get(i); 228 if (v.getParent() == null) { 229 mNotificationIcons.addView(v, i, params); 230 } 231 } 232 233 // Resort notification icons 234 final int childCount = mNotificationIcons.getChildCount(); 235 for (int i = 0; i < childCount; i++) { 236 View actual = mNotificationIcons.getChildAt(i); 237 StatusBarIconView expected = toShow.get(i); 238 if (actual == expected) { 239 continue; 240 } 241 mNotificationIcons.removeView(expected); 242 mNotificationIcons.addView(expected, i); 243 } 244 245 applyNotificationIconsTint(); 246 } 247 hideSystemIconArea(boolean animate)248 public void hideSystemIconArea(boolean animate) { 249 animateHide(mSystemIconArea, animate); 250 } 251 showSystemIconArea(boolean animate)252 public void showSystemIconArea(boolean animate) { 253 animateShow(mSystemIconArea, animate); 254 } 255 hideNotificationIconArea(boolean animate)256 public void hideNotificationIconArea(boolean animate) { 257 animateHide(mNotificationIconArea, animate); 258 } 259 showNotificationIconArea(boolean animate)260 public void showNotificationIconArea(boolean animate) { 261 animateShow(mNotificationIconArea, animate); 262 } 263 setClockVisibility(boolean visible)264 public void setClockVisibility(boolean visible) { 265 mClock.setVisibility(visible ? View.VISIBLE : View.GONE); 266 } 267 dump(PrintWriter pw)268 public void dump(PrintWriter pw) { 269 int N = mStatusIcons.getChildCount(); 270 pw.println(" system icons: " + N); 271 for (int i=0; i<N; i++) { 272 StatusBarIconView ic = (StatusBarIconView) mStatusIcons.getChildAt(i); 273 pw.println(" [" + i + "] icon=" + ic); 274 } 275 } 276 dispatchDemoCommand(String command, Bundle args)277 public void dispatchDemoCommand(String command, Bundle args) { 278 if (mDemoStatusIcons == null) { 279 mDemoStatusIcons = new DemoStatusIcons(mStatusIcons, mIconSize); 280 } 281 mDemoStatusIcons.dispatchDemoCommand(command, args); 282 } 283 284 /** 285 * Hides a view. 286 */ animateHide(final View v, boolean animate)287 private void animateHide(final View v, boolean animate) { 288 v.animate().cancel(); 289 if (!animate) { 290 v.setAlpha(0f); 291 v.setVisibility(View.INVISIBLE); 292 return; 293 } 294 v.animate() 295 .alpha(0f) 296 .setDuration(160) 297 .setStartDelay(0) 298 .setInterpolator(PhoneStatusBar.ALPHA_OUT) 299 .withEndAction(new Runnable() { 300 @Override 301 public void run() { 302 v.setVisibility(View.INVISIBLE); 303 } 304 }); 305 } 306 307 /** 308 * Shows a view, and synchronizes the animation with Keyguard exit animations, if applicable. 309 */ animateShow(View v, boolean animate)310 private void animateShow(View v, boolean animate) { 311 v.animate().cancel(); 312 v.setVisibility(View.VISIBLE); 313 if (!animate) { 314 v.setAlpha(1f); 315 return; 316 } 317 v.animate() 318 .alpha(1f) 319 .setDuration(320) 320 .setInterpolator(PhoneStatusBar.ALPHA_IN) 321 .setStartDelay(50) 322 323 // We need to clean up any pending end action from animateHide if we call 324 // both hide and show in the same frame before the animation actually gets started. 325 // cancel() doesn't really remove the end action. 326 .withEndAction(null); 327 328 // Synchronize the motion with the Keyguard fading if necessary. 329 if (mPhoneStatusBar.isKeyguardFadingAway()) { 330 v.animate() 331 .setDuration(mPhoneStatusBar.getKeyguardFadingAwayDuration()) 332 .setInterpolator(mLinearOutSlowIn) 333 .setStartDelay(mPhoneStatusBar.getKeyguardFadingAwayDelay()) 334 .start(); 335 } 336 } 337 setIconsDark(boolean dark)338 public void setIconsDark(boolean dark) { 339 if (mTransitionPending) { 340 deferIconTintChange(dark ? 1.0f : 0.0f); 341 } else if (mTransitionDeferring) { 342 animateIconTint(dark ? 1.0f : 0.0f, 343 Math.max(0, mTransitionDeferringStartTime - SystemClock.uptimeMillis()), 344 mTransitionDeferringDuration); 345 } else { 346 animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION); 347 } 348 } 349 animateIconTint(float targetDarkIntensity, long delay, long duration)350 private void animateIconTint(float targetDarkIntensity, long delay, 351 long duration) { 352 if (mTintAnimator != null) { 353 mTintAnimator.cancel(); 354 } 355 if (mDarkIntensity == targetDarkIntensity) { 356 return; 357 } 358 mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity); 359 mTintAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 360 @Override 361 public void onAnimationUpdate(ValueAnimator animation) { 362 setIconTintInternal((Float) animation.getAnimatedValue()); 363 } 364 }); 365 mTintAnimator.setDuration(duration); 366 mTintAnimator.setStartDelay(delay); 367 mTintAnimator.setInterpolator(mFastOutSlowIn); 368 mTintAnimator.start(); 369 } 370 setIconTintInternal(float darkIntensity)371 private void setIconTintInternal(float darkIntensity) { 372 mDarkIntensity = darkIntensity; 373 mIconTint = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, 374 mLightModeIconColorSingleTone, mDarkModeIconColorSingleTone); 375 applyIconTint(); 376 } 377 deferIconTintChange(float darkIntensity)378 private void deferIconTintChange(float darkIntensity) { 379 if (mTintChangePending && darkIntensity == mPendingDarkIntensity) { 380 return; 381 } 382 mTintChangePending = true; 383 mPendingDarkIntensity = darkIntensity; 384 } 385 applyIconTint()386 private void applyIconTint() { 387 for (int i = 0; i < mStatusIcons.getChildCount(); i++) { 388 StatusBarIconView v = (StatusBarIconView) mStatusIcons.getChildAt(i); 389 v.setImageTintList(ColorStateList.valueOf(mIconTint)); 390 } 391 mSignalCluster.setIconTint(mIconTint, mDarkIntensity); 392 mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint)); 393 mBatteryMeterView.setDarkIntensity(mDarkIntensity); 394 mClock.setTextColor(mIconTint); 395 applyNotificationIconsTint(); 396 } 397 applyNotificationIconsTint()398 private void applyNotificationIconsTint() { 399 for (int i = 0; i < mNotificationIcons.getChildCount(); i++) { 400 StatusBarIconView v = (StatusBarIconView) mNotificationIcons.getChildAt(i); 401 boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L)); 402 boolean colorize = !isPreL || isGrayscale(v); 403 if (colorize) { 404 v.setImageTintList(ColorStateList.valueOf(mIconTint)); 405 } 406 } 407 } 408 isGrayscale(StatusBarIconView v)409 private boolean isGrayscale(StatusBarIconView v) { 410 Object isGrayscale = v.getTag(R.id.icon_is_grayscale); 411 if (isGrayscale != null) { 412 return Boolean.TRUE.equals(isGrayscale); 413 } 414 boolean grayscale = mNotificationColorUtil.isGrayscaleIcon(v.getDrawable()); 415 v.setTag(R.id.icon_is_grayscale, grayscale); 416 return grayscale; 417 } 418 appTransitionPending()419 public void appTransitionPending() { 420 mTransitionPending = true; 421 } 422 appTransitionCancelled()423 public void appTransitionCancelled() { 424 if (mTransitionPending && mTintChangePending) { 425 mTintChangePending = false; 426 animateIconTint(mPendingDarkIntensity, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION); 427 } 428 mTransitionPending = false; 429 } 430 appTransitionStarting(long startTime, long duration)431 public void appTransitionStarting(long startTime, long duration) { 432 if (mTransitionPending && mTintChangePending) { 433 mTintChangePending = false; 434 animateIconTint(mPendingDarkIntensity, 435 Math.max(0, startTime - SystemClock.uptimeMillis()), 436 duration); 437 438 } else if (mTransitionPending) { 439 440 // If we don't have a pending tint change yet, the change might come in the future until 441 // startTime is reached. 442 mTransitionDeferring = true; 443 mTransitionDeferringStartTime = startTime; 444 mTransitionDeferringDuration = duration; 445 mHandler.removeCallbacks(mTransitionDeferringDoneRunnable); 446 mHandler.postAtTime(mTransitionDeferringDoneRunnable, startTime); 447 } 448 mTransitionPending = false; 449 } 450 getIconBlacklist(String blackListStr)451 public static ArraySet<String> getIconBlacklist(String blackListStr) { 452 ArraySet<String> ret = new ArraySet<String>(); 453 if (blackListStr != null) { 454 String[] blacklist = blackListStr.split(","); 455 for (String slot : blacklist) { 456 if (!TextUtils.isEmpty(slot)) { 457 ret.add(slot); 458 } 459 } 460 } 461 return ret; 462 } 463 } 464