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