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