1 /* 2 * Copyright (C) 2017 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 package com.android.systemui.statusbar; 17 18 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; 19 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_MEDIA_FAKE_ARTWORK; 20 import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_LOCKSCREEN_WALLPAPER; 21 import static com.android.systemui.statusbar.phone.StatusBar.SHOW_LOCKSCREEN_MEDIA_ARTWORK; 22 23 import android.annotation.MainThread; 24 import android.annotation.Nullable; 25 import android.app.Notification; 26 import android.content.Context; 27 import android.graphics.Bitmap; 28 import android.graphics.drawable.BitmapDrawable; 29 import android.graphics.drawable.ColorDrawable; 30 import android.graphics.drawable.Drawable; 31 import android.graphics.drawable.Icon; 32 import android.media.MediaMetadata; 33 import android.media.session.MediaController; 34 import android.media.session.MediaSession; 35 import android.media.session.MediaSessionManager; 36 import android.media.session.PlaybackState; 37 import android.os.AsyncTask; 38 import android.os.Trace; 39 import android.os.UserHandle; 40 import android.provider.DeviceConfig; 41 import android.provider.DeviceConfig.Properties; 42 import android.util.ArraySet; 43 import android.util.Log; 44 import android.view.View; 45 import android.widget.ImageView; 46 47 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; 48 import com.android.internal.statusbar.NotificationVisibility; 49 import com.android.systemui.Dependency; 50 import com.android.systemui.Dumpable; 51 import com.android.systemui.Interpolators; 52 import com.android.systemui.colorextraction.SysuiColorExtractor; 53 import com.android.systemui.dagger.qualifiers.Main; 54 import com.android.systemui.media.MediaDataManager; 55 import com.android.systemui.plugins.statusbar.StatusBarStateController; 56 import com.android.systemui.statusbar.dagger.StatusBarModule; 57 import com.android.systemui.statusbar.notification.NotificationEntryListener; 58 import com.android.systemui.statusbar.notification.NotificationEntryManager; 59 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 60 import com.android.systemui.statusbar.phone.BiometricUnlockController; 61 import com.android.systemui.statusbar.phone.KeyguardBypassController; 62 import com.android.systemui.statusbar.phone.LockscreenWallpaper; 63 import com.android.systemui.statusbar.phone.NotificationShadeWindowController; 64 import com.android.systemui.statusbar.phone.ScrimController; 65 import com.android.systemui.statusbar.phone.ScrimState; 66 import com.android.systemui.statusbar.phone.StatusBar; 67 import com.android.systemui.statusbar.policy.KeyguardStateController; 68 import com.android.systemui.util.DeviceConfigProxy; 69 import com.android.systemui.util.Utils; 70 import com.android.systemui.util.concurrency.DelayableExecutor; 71 72 import java.io.FileDescriptor; 73 import java.io.PrintWriter; 74 import java.lang.ref.WeakReference; 75 import java.util.ArrayList; 76 import java.util.Collection; 77 import java.util.HashSet; 78 import java.util.List; 79 import java.util.Set; 80 81 import dagger.Lazy; 82 83 /** 84 * Handles tasks and state related to media notifications. For example, there is a 'current' media 85 * notification, which this class keeps track of. 86 */ 87 public class NotificationMediaManager implements Dumpable { 88 private static final String TAG = "NotificationMediaManager"; 89 public static final boolean DEBUG_MEDIA = false; 90 91 private final StatusBarStateController mStatusBarStateController 92 = Dependency.get(StatusBarStateController.class); 93 private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class); 94 private final KeyguardStateController mKeyguardStateController = Dependency.get( 95 KeyguardStateController.class); 96 private final KeyguardBypassController mKeyguardBypassController; 97 private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>(); 98 static { 99 PAUSED_MEDIA_STATES.add(PlaybackState.STATE_NONE); 100 PAUSED_MEDIA_STATES.add(PlaybackState.STATE_STOPPED); 101 PAUSED_MEDIA_STATES.add(PlaybackState.STATE_PAUSED); 102 PAUSED_MEDIA_STATES.add(PlaybackState.STATE_ERROR); 103 PAUSED_MEDIA_STATES.add(PlaybackState.STATE_CONNECTING); 104 } 105 106 private final NotificationEntryManager mEntryManager; 107 private final MediaDataManager mMediaDataManager; 108 109 @Nullable 110 private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController; 111 112 @Nullable 113 private BiometricUnlockController mBiometricUnlockController; 114 @Nullable 115 private ScrimController mScrimController; 116 @Nullable 117 private LockscreenWallpaper mLockscreenWallpaper; 118 119 private final DelayableExecutor mMainExecutor; 120 121 private final Context mContext; 122 private final MediaSessionManager mMediaSessionManager; 123 private final ArrayList<MediaListener> mMediaListeners; 124 private final Lazy<StatusBar> mStatusBarLazy; 125 private final MediaArtworkProcessor mMediaArtworkProcessor; 126 private final Set<AsyncTask<?, ?, ?>> mProcessArtworkTasks = new ArraySet<>(); 127 128 protected NotificationPresenter mPresenter; 129 private MediaController mMediaController; 130 private String mMediaNotificationKey; 131 private MediaMetadata mMediaMetadata; 132 133 private BackDropView mBackdrop; 134 private ImageView mBackdropFront; 135 private ImageView mBackdropBack; 136 137 private boolean mShowCompactMediaSeekbar; 138 private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener = 139 new DeviceConfig.OnPropertiesChangedListener() { 140 @Override 141 public void onPropertiesChanged(Properties properties) { 142 for (String name : properties.getKeyset()) { 143 if (SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED.equals(name)) { 144 String value = properties.getString(name, null); 145 if (DEBUG_MEDIA) { 146 Log.v(TAG, "DEBUG_MEDIA: compact media seekbar flag updated: " + value); 147 } 148 mShowCompactMediaSeekbar = "true".equals(value); 149 } 150 } 151 } 152 }; 153 154 private final MediaController.Callback mMediaListener = new MediaController.Callback() { 155 @Override 156 public void onPlaybackStateChanged(PlaybackState state) { 157 super.onPlaybackStateChanged(state); 158 if (DEBUG_MEDIA) { 159 Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state); 160 } 161 if (state != null) { 162 if (!isPlaybackActive(state.getState())) { 163 clearCurrentMediaNotification(); 164 } 165 findAndUpdateMediaNotifications(); 166 } 167 } 168 169 @Override 170 public void onMetadataChanged(MediaMetadata metadata) { 171 super.onMetadataChanged(metadata); 172 if (DEBUG_MEDIA) { 173 Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); 174 } 175 mMediaArtworkProcessor.clearCache(); 176 mMediaMetadata = metadata; 177 dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */); 178 } 179 }; 180 181 /** 182 * Injected constructor. See {@link StatusBarModule}. 183 */ NotificationMediaManager( Context context, Lazy<StatusBar> statusBarLazy, Lazy<NotificationShadeWindowController> notificationShadeWindowController, NotificationEntryManager notificationEntryManager, MediaArtworkProcessor mediaArtworkProcessor, KeyguardBypassController keyguardBypassController, @Main DelayableExecutor mainExecutor, DeviceConfigProxy deviceConfig, MediaDataManager mediaDataManager)184 public NotificationMediaManager( 185 Context context, 186 Lazy<StatusBar> statusBarLazy, 187 Lazy<NotificationShadeWindowController> notificationShadeWindowController, 188 NotificationEntryManager notificationEntryManager, 189 MediaArtworkProcessor mediaArtworkProcessor, 190 KeyguardBypassController keyguardBypassController, 191 @Main DelayableExecutor mainExecutor, 192 DeviceConfigProxy deviceConfig, 193 MediaDataManager mediaDataManager) { 194 mContext = context; 195 mMediaArtworkProcessor = mediaArtworkProcessor; 196 mKeyguardBypassController = keyguardBypassController; 197 mMediaListeners = new ArrayList<>(); 198 // TODO: use MediaSessionManager.SessionListener to hook us up to future updates 199 // in session state 200 mMediaSessionManager = (MediaSessionManager) mContext.getSystemService( 201 Context.MEDIA_SESSION_SERVICE); 202 // TODO: use KeyguardStateController#isOccluded to remove this dependency 203 mStatusBarLazy = statusBarLazy; 204 mNotificationShadeWindowController = notificationShadeWindowController; 205 mEntryManager = notificationEntryManager; 206 mMainExecutor = mainExecutor; 207 mMediaDataManager = mediaDataManager; 208 209 notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() { 210 211 @Override 212 public void onPendingEntryAdded(NotificationEntry entry) { 213 mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn()); 214 } 215 216 @Override 217 public void onPreEntryUpdated(NotificationEntry entry) { 218 mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn()); 219 } 220 221 @Override 222 public void onEntryInflated(NotificationEntry entry) { 223 findAndUpdateMediaNotifications(); 224 } 225 226 @Override 227 public void onEntryReinflated(NotificationEntry entry) { 228 findAndUpdateMediaNotifications(); 229 } 230 231 @Override 232 public void onEntryRemoved( 233 NotificationEntry entry, 234 NotificationVisibility visibility, 235 boolean removedByUser, 236 int reason) { 237 onNotificationRemoved(entry.getKey()); 238 mediaDataManager.onNotificationRemoved(entry.getKey()); 239 } 240 }); 241 242 mShowCompactMediaSeekbar = "true".equals( 243 DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI, 244 SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED)); 245 246 deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, 247 mContext.getMainExecutor(), 248 mPropertiesChangedListener); 249 } 250 251 /** 252 * Check if a state should be considered actively playing 253 * @param state a PlaybackState 254 * @return true if playing 255 */ isPlayingState(int state)256 public static boolean isPlayingState(int state) { 257 return !PAUSED_MEDIA_STATES.contains(state); 258 } 259 setUpWithPresenter(NotificationPresenter presenter)260 public void setUpWithPresenter(NotificationPresenter presenter) { 261 mPresenter = presenter; 262 } 263 onNotificationRemoved(String key)264 public void onNotificationRemoved(String key) { 265 if (key.equals(mMediaNotificationKey)) { 266 clearCurrentMediaNotification(); 267 dispatchUpdateMediaMetaData(true /* changed */, true /* allowEnterAnimation */); 268 } 269 } 270 getMediaNotificationKey()271 public String getMediaNotificationKey() { 272 return mMediaNotificationKey; 273 } 274 getMediaMetadata()275 public MediaMetadata getMediaMetadata() { 276 return mMediaMetadata; 277 } 278 getShowCompactMediaSeekbar()279 public boolean getShowCompactMediaSeekbar() { 280 return mShowCompactMediaSeekbar; 281 } 282 getMediaIcon()283 public Icon getMediaIcon() { 284 if (mMediaNotificationKey == null) { 285 return null; 286 } 287 synchronized (mEntryManager) { 288 NotificationEntry entry = mEntryManager 289 .getActiveNotificationUnfiltered(mMediaNotificationKey); 290 if (entry == null || entry.getIcons().getShelfIcon() == null) { 291 return null; 292 } 293 294 return entry.getIcons().getShelfIcon().getSourceIcon(); 295 } 296 } 297 addCallback(MediaListener callback)298 public void addCallback(MediaListener callback) { 299 mMediaListeners.add(callback); 300 callback.onPrimaryMetadataOrStateChanged(mMediaMetadata, 301 getMediaControllerPlaybackState(mMediaController)); 302 } 303 removeCallback(MediaListener callback)304 public void removeCallback(MediaListener callback) { 305 mMediaListeners.remove(callback); 306 } 307 findAndUpdateMediaNotifications()308 public void findAndUpdateMediaNotifications() { 309 boolean metaDataChanged = false; 310 311 synchronized (mEntryManager) { 312 Collection<NotificationEntry> allNotifications = mEntryManager.getAllNotifs(); 313 314 // Promote the media notification with a controller in 'playing' state, if any. 315 NotificationEntry mediaNotification = null; 316 MediaController controller = null; 317 for (NotificationEntry entry : allNotifications) { 318 if (entry.isMediaNotification()) { 319 final MediaSession.Token token = 320 entry.getSbn().getNotification().extras.getParcelable( 321 Notification.EXTRA_MEDIA_SESSION); 322 if (token != null) { 323 MediaController aController = new MediaController(mContext, token); 324 if (PlaybackState.STATE_PLAYING == 325 getMediaControllerPlaybackState(aController)) { 326 if (DEBUG_MEDIA) { 327 Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching " 328 + entry.getSbn().getKey()); 329 } 330 mediaNotification = entry; 331 controller = aController; 332 break; 333 } 334 } 335 } 336 } 337 if (mediaNotification == null) { 338 // Still nothing? OK, let's just look for live media sessions and see if they match 339 // one of our notifications. This will catch apps that aren't (yet!) using media 340 // notifications. 341 342 if (mMediaSessionManager != null) { 343 // TODO: Should this really be for all users? 344 final List<MediaController> sessions 345 = mMediaSessionManager.getActiveSessionsForUser( 346 null, 347 UserHandle.USER_ALL); 348 349 for (MediaController aController : sessions) { 350 // now to see if we have one like this 351 final String pkg = aController.getPackageName(); 352 353 for (NotificationEntry entry : allNotifications) { 354 if (entry.getSbn().getPackageName().equals(pkg)) { 355 if (DEBUG_MEDIA) { 356 Log.v(TAG, "DEBUG_MEDIA: found controller matching " 357 + entry.getSbn().getKey()); 358 } 359 controller = aController; 360 mediaNotification = entry; 361 break; 362 } 363 } 364 } 365 } 366 } 367 368 if (controller != null && !sameSessions(mMediaController, controller)) { 369 // We have a new media session 370 clearCurrentMediaNotificationSession(); 371 mMediaController = controller; 372 mMediaController.registerCallback(mMediaListener); 373 mMediaMetadata = mMediaController.getMetadata(); 374 if (DEBUG_MEDIA) { 375 Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: " 376 + mMediaController + ", receive metadata: " + mMediaMetadata); 377 } 378 379 metaDataChanged = true; 380 } 381 382 if (mediaNotification != null 383 && !mediaNotification.getSbn().getKey().equals(mMediaNotificationKey)) { 384 mMediaNotificationKey = mediaNotification.getSbn().getKey(); 385 if (DEBUG_MEDIA) { 386 Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key=" 387 + mMediaNotificationKey); 388 } 389 } 390 } 391 392 if (metaDataChanged) { 393 mEntryManager.updateNotifications("NotificationMediaManager - metaDataChanged"); 394 } 395 396 dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */); 397 } 398 clearCurrentMediaNotification()399 public void clearCurrentMediaNotification() { 400 mMediaNotificationKey = null; 401 clearCurrentMediaNotificationSession(); 402 } 403 dispatchUpdateMediaMetaData(boolean changed, boolean allowEnterAnimation)404 private void dispatchUpdateMediaMetaData(boolean changed, boolean allowEnterAnimation) { 405 if (mPresenter != null) { 406 mPresenter.updateMediaMetaData(changed, allowEnterAnimation); 407 } 408 @PlaybackState.State int state = getMediaControllerPlaybackState(mMediaController); 409 ArrayList<MediaListener> callbacks = new ArrayList<>(mMediaListeners); 410 for (int i = 0; i < callbacks.size(); i++) { 411 callbacks.get(i).onPrimaryMetadataOrStateChanged(mMediaMetadata, state); 412 } 413 } 414 415 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)416 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 417 pw.print(" mMediaSessionManager="); 418 pw.println(mMediaSessionManager); 419 pw.print(" mMediaNotificationKey="); 420 pw.println(mMediaNotificationKey); 421 pw.print(" mMediaController="); 422 pw.print(mMediaController); 423 if (mMediaController != null) { 424 pw.print(" state=" + mMediaController.getPlaybackState()); 425 } 426 pw.println(); 427 pw.print(" mMediaMetadata="); 428 pw.print(mMediaMetadata); 429 if (mMediaMetadata != null) { 430 pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE)); 431 } 432 pw.println(); 433 } 434 isPlaybackActive(int state)435 private boolean isPlaybackActive(int state) { 436 return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR 437 && state != PlaybackState.STATE_NONE; 438 } 439 sameSessions(MediaController a, MediaController b)440 private boolean sameSessions(MediaController a, MediaController b) { 441 if (a == b) { 442 return true; 443 } 444 if (a == null) { 445 return false; 446 } 447 return a.controlsSameSession(b); 448 } 449 getMediaControllerPlaybackState(MediaController controller)450 private int getMediaControllerPlaybackState(MediaController controller) { 451 if (controller != null) { 452 final PlaybackState playbackState = controller.getPlaybackState(); 453 if (playbackState != null) { 454 return playbackState.getState(); 455 } 456 } 457 return PlaybackState.STATE_NONE; 458 } 459 clearCurrentMediaNotificationSession()460 private void clearCurrentMediaNotificationSession() { 461 mMediaArtworkProcessor.clearCache(); 462 mMediaMetadata = null; 463 if (mMediaController != null) { 464 if (DEBUG_MEDIA) { 465 Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: " 466 + mMediaController.getPackageName()); 467 } 468 mMediaController.unregisterCallback(mMediaListener); 469 } 470 mMediaController = null; 471 } 472 473 /** 474 * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper. 475 */ updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation)476 public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { 477 Trace.beginSection("StatusBar#updateMediaMetaData"); 478 if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) { 479 Trace.endSection(); 480 return; 481 } 482 483 if (mBackdrop == null) { 484 Trace.endSection(); 485 return; // called too early 486 } 487 488 boolean wakeAndUnlock = mBiometricUnlockController != null 489 && mBiometricUnlockController.isWakeAndUnlock(); 490 if (mKeyguardStateController.isLaunchTransitionFadingAway() || wakeAndUnlock) { 491 mBackdrop.setVisibility(View.INVISIBLE); 492 Trace.endSection(); 493 return; 494 } 495 496 MediaMetadata mediaMetadata = getMediaMetadata(); 497 498 if (DEBUG_MEDIA) { 499 Log.v(TAG, "DEBUG_MEDIA: updating album art for notification " 500 + getMediaNotificationKey() 501 + " metadata=" + mediaMetadata 502 + " metaDataChanged=" + metaDataChanged 503 + " state=" + mStatusBarStateController.getState()); 504 } 505 506 Bitmap artworkBitmap = null; 507 if (mediaMetadata != null && !mKeyguardBypassController.getBypassEnabled()) { 508 artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART); 509 if (artworkBitmap == null) { 510 artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); 511 } 512 } 513 514 // Process artwork on a background thread and send the resulting bitmap to 515 // finishUpdateMediaMetaData. 516 if (metaDataChanged) { 517 for (AsyncTask<?, ?, ?> task : mProcessArtworkTasks) { 518 task.cancel(true); 519 } 520 mProcessArtworkTasks.clear(); 521 } 522 if (artworkBitmap != null && !Utils.useQsMediaPlayer(mContext)) { 523 mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged, 524 allowEnterAnimation).execute(artworkBitmap)); 525 } else { 526 finishUpdateMediaMetaData(metaDataChanged, allowEnterAnimation, null); 527 } 528 529 Trace.endSection(); 530 } 531 finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation, @Nullable Bitmap bmp)532 private void finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation, 533 @Nullable Bitmap bmp) { 534 Drawable artworkDrawable = null; 535 if (bmp != null) { 536 artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp); 537 } 538 boolean hasMediaArtwork = artworkDrawable != null; 539 boolean allowWhenShade = false; 540 if (ENABLE_LOCKSCREEN_WALLPAPER && artworkDrawable == null) { 541 Bitmap lockWallpaper = 542 mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null; 543 if (lockWallpaper != null) { 544 artworkDrawable = new LockscreenWallpaper.WallpaperDrawable( 545 mBackdropBack.getResources(), lockWallpaper); 546 // We're in the SHADE mode on the SIM screen - yet we still need to show 547 // the lockscreen wallpaper in that mode. 548 allowWhenShade = mStatusBarStateController.getState() == KEYGUARD; 549 } 550 } 551 552 NotificationShadeWindowController windowController = 553 mNotificationShadeWindowController.get(); 554 boolean hideBecauseOccluded = mStatusBarLazy.get().isOccluded(); 555 556 final boolean hasArtwork = artworkDrawable != null; 557 mColorExtractor.setHasMediaArtwork(hasMediaArtwork); 558 if (mScrimController != null) { 559 mScrimController.setHasBackdrop(hasArtwork); 560 } 561 562 if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) 563 && (mStatusBarStateController.getState() != StatusBarState.SHADE || allowWhenShade) 564 && mBiometricUnlockController != null && mBiometricUnlockController.getMode() 565 != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING 566 && !hideBecauseOccluded) { 567 // time to show some art! 568 if (mBackdrop.getVisibility() != View.VISIBLE) { 569 mBackdrop.setVisibility(View.VISIBLE); 570 if (allowEnterAnimation) { 571 mBackdrop.setAlpha(0); 572 mBackdrop.animate().alpha(1f); 573 } else { 574 mBackdrop.animate().cancel(); 575 mBackdrop.setAlpha(1f); 576 } 577 if (windowController != null) { 578 windowController.setBackdropShowing(true); 579 } 580 metaDataChanged = true; 581 if (DEBUG_MEDIA) { 582 Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork"); 583 } 584 } 585 if (metaDataChanged) { 586 if (mBackdropBack.getDrawable() != null) { 587 Drawable drawable = 588 mBackdropBack.getDrawable().getConstantState() 589 .newDrawable(mBackdropFront.getResources()).mutate(); 590 mBackdropFront.setImageDrawable(drawable); 591 mBackdropFront.setAlpha(1f); 592 mBackdropFront.setVisibility(View.VISIBLE); 593 } else { 594 mBackdropFront.setVisibility(View.INVISIBLE); 595 } 596 597 if (DEBUG_MEDIA_FAKE_ARTWORK) { 598 final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF); 599 Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c)); 600 mBackdropBack.setBackgroundColor(0xFFFFFFFF); 601 mBackdropBack.setImageDrawable(new ColorDrawable(c)); 602 } else { 603 mBackdropBack.setImageDrawable(artworkDrawable); 604 } 605 606 if (mBackdropFront.getVisibility() == View.VISIBLE) { 607 if (DEBUG_MEDIA) { 608 Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from " 609 + mBackdropFront.getDrawable() 610 + " to " 611 + mBackdropBack.getDrawable()); 612 } 613 mBackdropFront.animate() 614 .setDuration(250) 615 .alpha(0f).withEndAction(mHideBackdropFront); 616 } 617 } 618 } else { 619 // need to hide the album art, either because we are unlocked, on AOD 620 // or because the metadata isn't there to support it 621 if (mBackdrop.getVisibility() != View.GONE) { 622 if (DEBUG_MEDIA) { 623 Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork"); 624 } 625 boolean cannotAnimateDoze = mStatusBarStateController.isDozing() 626 && !ScrimState.AOD.getAnimateChange(); 627 boolean needsBypassFading = mKeyguardStateController.isBypassFadingAnimation(); 628 if (((mBiometricUnlockController != null && mBiometricUnlockController.getMode() 629 == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING 630 || cannotAnimateDoze) && !needsBypassFading) 631 || hideBecauseOccluded) { 632 633 // We are unlocking directly - no animation! 634 mBackdrop.setVisibility(View.GONE); 635 mBackdropBack.setImageDrawable(null); 636 if (windowController != null) { 637 windowController.setBackdropShowing(false); 638 } 639 } else { 640 if (windowController != null) { 641 windowController.setBackdropShowing(false); 642 } 643 mBackdrop.animate() 644 .alpha(0) 645 .setInterpolator(Interpolators.ACCELERATE_DECELERATE) 646 .setDuration(300) 647 .setStartDelay(0) 648 .withEndAction(() -> { 649 mBackdrop.setVisibility(View.GONE); 650 mBackdropFront.animate().cancel(); 651 mBackdropBack.setImageDrawable(null); 652 mMainExecutor.execute(mHideBackdropFront); 653 }); 654 if (mKeyguardStateController.isKeyguardFadingAway()) { 655 mBackdrop.animate() 656 .setDuration( 657 mKeyguardStateController.getShortenedFadingAwayDuration()) 658 .setStartDelay( 659 mKeyguardStateController.getKeyguardFadingAwayDelay()) 660 .setInterpolator(Interpolators.LINEAR) 661 .start(); 662 } 663 } 664 } 665 } 666 } 667 setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack, ScrimController scrimController, LockscreenWallpaper lockscreenWallpaper)668 public void setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack, 669 ScrimController scrimController, LockscreenWallpaper lockscreenWallpaper) { 670 mBackdrop = backdrop; 671 mBackdropFront = backdropFront; 672 mBackdropBack = backdropBack; 673 mScrimController = scrimController; 674 mLockscreenWallpaper = lockscreenWallpaper; 675 } 676 setBiometricUnlockController(BiometricUnlockController biometricUnlockController)677 public void setBiometricUnlockController(BiometricUnlockController biometricUnlockController) { 678 mBiometricUnlockController = biometricUnlockController; 679 } 680 681 /** 682 * Hide the album artwork that is fading out and release its bitmap. 683 */ 684 protected final Runnable mHideBackdropFront = new Runnable() { 685 @Override 686 public void run() { 687 if (DEBUG_MEDIA) { 688 Log.v(TAG, "DEBUG_MEDIA: removing fade layer"); 689 } 690 mBackdropFront.setVisibility(View.INVISIBLE); 691 mBackdropFront.animate().cancel(); 692 mBackdropFront.setImageDrawable(null); 693 } 694 }; 695 processArtwork(Bitmap artwork)696 private Bitmap processArtwork(Bitmap artwork) { 697 return mMediaArtworkProcessor.processArtwork(mContext, artwork); 698 } 699 700 @MainThread removeTask(AsyncTask<?, ?, ?> task)701 private void removeTask(AsyncTask<?, ?, ?> task) { 702 mProcessArtworkTasks.remove(task); 703 } 704 705 /** 706 * {@link AsyncTask} to prepare album art for use as backdrop on lock screen. 707 */ 708 private static final class ProcessArtworkTask extends AsyncTask<Bitmap, Void, Bitmap> { 709 710 private final WeakReference<NotificationMediaManager> mManagerRef; 711 private final boolean mMetaDataChanged; 712 private final boolean mAllowEnterAnimation; 713 ProcessArtworkTask(NotificationMediaManager manager, boolean changed, boolean allowAnimation)714 ProcessArtworkTask(NotificationMediaManager manager, boolean changed, 715 boolean allowAnimation) { 716 mManagerRef = new WeakReference<>(manager); 717 mMetaDataChanged = changed; 718 mAllowEnterAnimation = allowAnimation; 719 } 720 721 @Override doInBackground(Bitmap... bitmaps)722 protected Bitmap doInBackground(Bitmap... bitmaps) { 723 NotificationMediaManager manager = mManagerRef.get(); 724 if (manager == null || bitmaps.length == 0 || isCancelled()) { 725 return null; 726 } 727 return manager.processArtwork(bitmaps[0]); 728 } 729 730 @Override onPostExecute(@ullable Bitmap result)731 protected void onPostExecute(@Nullable Bitmap result) { 732 NotificationMediaManager manager = mManagerRef.get(); 733 if (manager != null && !isCancelled()) { 734 manager.removeTask(this); 735 manager.finishUpdateMediaMetaData(mMetaDataChanged, mAllowEnterAnimation, result); 736 } 737 } 738 739 @Override onCancelled(Bitmap result)740 protected void onCancelled(Bitmap result) { 741 if (result != null) { 742 result.recycle(); 743 } 744 NotificationMediaManager manager = mManagerRef.get(); 745 if (manager != null) { 746 manager.removeTask(this); 747 } 748 } 749 } 750 751 public interface MediaListener { 752 /** 753 * Called whenever there's new metadata or playback state. 754 * @param metadata Current metadata. 755 * @param state Current playback state 756 * @see PlaybackState.State 757 */ onPrimaryMetadataOrStateChanged(MediaMetadata metadata, @PlaybackState.State int state)758 default void onPrimaryMetadataOrStateChanged(MediaMetadata metadata, 759 @PlaybackState.State int state) {} 760 } 761 } 762