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