Fix memory leak of MediaControlPanel
MediaControlPanel instances were being leak through the ContentObserver registration. This fixes the leak by moving ContentObserver into owning singleton which then notifies active MediaControlPanel instances. Bug: 288244587 Test: updated unit tests Change-Id: Idb1f3a750e6e87c56c645f53ce678a62e7181f11
This commit is contained in:
@@ -21,6 +21,8 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.database.ContentObserver
|
||||||
|
import android.provider.Settings
|
||||||
import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
|
import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.MathUtils
|
import android.util.MathUtils
|
||||||
@@ -64,6 +66,7 @@ import com.android.systemui.util.Utils
|
|||||||
import com.android.systemui.util.animation.UniqueObjectHostView
|
import com.android.systemui.util.animation.UniqueObjectHostView
|
||||||
import com.android.systemui.util.animation.requiresRemeasuring
|
import com.android.systemui.util.animation.requiresRemeasuring
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
|
import com.android.systemui.util.settings.GlobalSettings
|
||||||
import com.android.systemui.util.time.SystemClock
|
import com.android.systemui.util.time.SystemClock
|
||||||
import com.android.systemui.util.traceSection
|
import com.android.systemui.util.traceSection
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
@@ -105,6 +108,7 @@ constructor(
|
|||||||
private val mediaFlags: MediaFlags,
|
private val mediaFlags: MediaFlags,
|
||||||
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
||||||
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
|
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
|
||||||
|
private val globalSettings: GlobalSettings,
|
||||||
) : Dumpable {
|
) : Dumpable {
|
||||||
/** The current width of the carousel */
|
/** The current width of the carousel */
|
||||||
var currentCarouselWidth: Int = 0
|
var currentCarouselWidth: Int = 0
|
||||||
@@ -169,6 +173,13 @@ constructor(
|
|||||||
|
|
||||||
private var carouselLocale: Locale? = null
|
private var carouselLocale: Locale? = null
|
||||||
|
|
||||||
|
private val animationScaleObserver: ContentObserver =
|
||||||
|
object : ContentObserver(null) {
|
||||||
|
override fun onChange(selfChange: Boolean) {
|
||||||
|
MediaPlayerData.players().forEach { it.updateAnimatorDurationScale() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Whether the media card currently has the "expanded" layout */
|
/** Whether the media card currently has the "expanded" layout */
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
var currentlyExpanded = true
|
var currentlyExpanded = true
|
||||||
@@ -529,6 +540,12 @@ constructor(
|
|||||||
listenForAnyStateToGoneKeyguardTransition(this)
|
listenForAnyStateToGoneKeyguardTransition(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notifies all active players about animation scale changes.
|
||||||
|
globalSettings.registerContentObserver(
|
||||||
|
Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
|
||||||
|
animationScaleObserver
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inflateSettingsButton() {
|
private fun inflateSettingsButton() {
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import android.content.pm.PackageManager;
|
|||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.database.ContentObserver;
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BlendMode;
|
import android.graphics.BlendMode;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
@@ -252,13 +251,6 @@ public class MediaControlPanel {
|
|||||||
private boolean mWasPlaying = false;
|
private boolean mWasPlaying = false;
|
||||||
private boolean mButtonClicked = false;
|
private boolean mButtonClicked = false;
|
||||||
|
|
||||||
private ContentObserver mAnimationScaleObserver = new ContentObserver(null) {
|
|
||||||
@Override
|
|
||||||
public void onChange(boolean selfChange) {
|
|
||||||
updateAnimatorDurationScale();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a new control panel
|
* Initialize a new control panel
|
||||||
*
|
*
|
||||||
@@ -318,10 +310,6 @@ public class MediaControlPanel {
|
|||||||
mFeatureFlags = featureFlags;
|
mFeatureFlags = featureFlags;
|
||||||
|
|
||||||
mGlobalSettings = globalSettings;
|
mGlobalSettings = globalSettings;
|
||||||
mGlobalSettings.registerContentObserver(
|
|
||||||
Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
|
|
||||||
mAnimationScaleObserver
|
|
||||||
);
|
|
||||||
updateAnimatorDurationScale();
|
updateAnimatorDurationScale();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,7 +393,10 @@ public class MediaControlPanel {
|
|||||||
updateSeekBarVisibility();
|
updateSeekBarVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateAnimatorDurationScale() {
|
/**
|
||||||
|
* Reloads animator duration scale.
|
||||||
|
*/
|
||||||
|
void updateAnimatorDurationScale() {
|
||||||
if (mSeekBarObserver != null) {
|
if (mSeekBarObserver != null) {
|
||||||
mSeekBarObserver.setAnimationEnabled(
|
mSeekBarObserver.setAnimationEnabled(
|
||||||
mGlobalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) > 0f);
|
mGlobalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) > 0f);
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ package com.android.systemui.media.controls.ui
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.database.ContentObserver
|
||||||
import android.os.LocaleList
|
import android.os.LocaleList
|
||||||
|
import android.provider.Settings
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import android.testing.TestableLooper
|
import android.testing.TestableLooper
|
||||||
import android.util.MathUtils.abs
|
import android.util.MathUtils.abs
|
||||||
@@ -56,6 +58,7 @@ import com.android.systemui.util.concurrency.DelayableExecutor
|
|||||||
import com.android.systemui.util.mockito.any
|
import com.android.systemui.util.mockito.any
|
||||||
import com.android.systemui.util.mockito.capture
|
import com.android.systemui.util.mockito.capture
|
||||||
import com.android.systemui.util.mockito.eq
|
import com.android.systemui.util.mockito.eq
|
||||||
|
import com.android.systemui.util.settings.GlobalSettings
|
||||||
import com.android.systemui.util.time.FakeSystemClock
|
import com.android.systemui.util.time.FakeSystemClock
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
@@ -113,6 +116,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
@Mock lateinit var mediaFlags: MediaFlags
|
@Mock lateinit var mediaFlags: MediaFlags
|
||||||
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
||||||
@Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
|
@Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
|
||||||
|
@Mock lateinit var globalSettings: GlobalSettings
|
||||||
private lateinit var transitionRepository: FakeKeyguardTransitionRepository
|
private lateinit var transitionRepository: FakeKeyguardTransitionRepository
|
||||||
@Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
|
@Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
|
||||||
@Captor
|
@Captor
|
||||||
@@ -120,6 +124,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
@Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener>
|
@Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener>
|
||||||
@Captor lateinit var keyguardCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
|
@Captor lateinit var keyguardCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
|
||||||
@Captor lateinit var hostStateCallback: ArgumentCaptor<MediaHostStatesManager.Callback>
|
@Captor lateinit var hostStateCallback: ArgumentCaptor<MediaHostStatesManager.Callback>
|
||||||
|
@Captor lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
|
||||||
|
|
||||||
private val clock = FakeSystemClock()
|
private val clock = FakeSystemClock()
|
||||||
private lateinit var mediaCarouselController: MediaCarouselController
|
private lateinit var mediaCarouselController: MediaCarouselController
|
||||||
@@ -148,6 +153,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
mediaFlags,
|
mediaFlags,
|
||||||
keyguardUpdateMonitor,
|
keyguardUpdateMonitor,
|
||||||
KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
|
KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
|
||||||
|
globalSettings
|
||||||
)
|
)
|
||||||
verify(configurationController).addCallback(capture(configListener))
|
verify(configurationController).addCallback(capture(configListener))
|
||||||
verify(mediaDataManager).addListener(capture(listener))
|
verify(mediaDataManager).addListener(capture(listener))
|
||||||
@@ -160,6 +166,11 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
|
whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
|
||||||
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
|
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
|
||||||
MediaPlayerData.clear()
|
MediaPlayerData.clear()
|
||||||
|
verify(globalSettings)
|
||||||
|
.registerContentObserver(
|
||||||
|
eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)),
|
||||||
|
settingsObserverCaptor.capture()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -873,6 +884,15 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
assertTrue(stateUpdated)
|
assertTrue(stateUpdated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAnimationScaleChanged_mediaControlPanelsNotified() {
|
||||||
|
MediaPlayerData.addMediaPlayer("key", DATA, panel, clock, isSsReactivated = false)
|
||||||
|
|
||||||
|
globalSettings.putFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 0f)
|
||||||
|
settingsObserverCaptor.value!!.onChange(false)
|
||||||
|
verify(panel).updateAnimatorDurationScale()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method when a configuration change occurs.
|
* Helper method when a configuration change occurs.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import android.content.Intent
|
|||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.database.ContentObserver
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
@@ -113,7 +112,6 @@ import org.junit.runner.RunWith
|
|||||||
import org.mockito.ArgumentCaptor
|
import org.mockito.ArgumentCaptor
|
||||||
import org.mockito.ArgumentMatchers.anyInt
|
import org.mockito.ArgumentMatchers.anyInt
|
||||||
import org.mockito.ArgumentMatchers.anyLong
|
import org.mockito.ArgumentMatchers.anyLong
|
||||||
import org.mockito.Captor
|
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.anyString
|
import org.mockito.Mockito.anyString
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.Mockito.mock
|
||||||
@@ -239,7 +237,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
|
this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
|
||||||
}
|
}
|
||||||
@Mock private lateinit var globalSettings: GlobalSettings
|
@Mock private lateinit var globalSettings: GlobalSettings
|
||||||
@Captor private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
|
|
||||||
|
|
||||||
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
||||||
|
|
||||||
@@ -281,7 +278,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
lockscreenUserManager,
|
lockscreenUserManager,
|
||||||
broadcastDialogController,
|
broadcastDialogController,
|
||||||
fakeFeatureFlag,
|
fakeFeatureFlag,
|
||||||
globalSettings,
|
globalSettings
|
||||||
) {
|
) {
|
||||||
override fun loadAnimator(
|
override fun loadAnimator(
|
||||||
animId: Int,
|
animId: Int,
|
||||||
@@ -292,12 +289,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(globalSettings)
|
|
||||||
.registerContentObserver(
|
|
||||||
eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)),
|
|
||||||
settingsObserverCaptor.capture()
|
|
||||||
)
|
|
||||||
|
|
||||||
initGutsViewHolderMocks()
|
initGutsViewHolderMocks()
|
||||||
initMediaViewHolderMocks()
|
initMediaViewHolderMocks()
|
||||||
|
|
||||||
@@ -986,7 +977,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
// When the setting changes,
|
// When the setting changes,
|
||||||
globalSettings.putFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 0f)
|
globalSettings.putFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 0f)
|
||||||
settingsObserverCaptor.value!!.onChange(false)
|
player.updateAnimatorDurationScale()
|
||||||
|
|
||||||
// Then the seekbar is set to not animate
|
// Then the seekbar is set to not animate
|
||||||
assertThat(seekBarObserver.animationEnabled).isFalse()
|
assertThat(seekBarObserver.animationEnabled).isFalse()
|
||||||
|
|||||||
Reference in New Issue
Block a user