Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3d1da3fe authored by Beth Thibodeau's avatar Beth Thibodeau Committed by Automerger Merge Worker
Browse files

Merge "Check URI permissions for resumable media artwork" into udc-qpr-dev am:...

Merge "Check URI permissions for resumable media artwork" into udc-qpr-dev am: c469cee9 am: 8b7300ae

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/24361993



Change-Id: I4cf6bc9621c9ab2d04952371f93b3369cf6e5f2f
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents f1b9b359 8b7300ae
Loading
Loading
Loading
Loading
+30 −3
Original line number Original line Diff line number Diff line
@@ -22,12 +22,14 @@ import android.app.Notification
import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
import android.app.PendingIntent
import android.app.PendingIntent
import android.app.StatusBarManager
import android.app.StatusBarManager
import android.app.UriGrantsManager
import android.app.smartspace.SmartspaceAction
import android.app.smartspace.SmartspaceAction
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
import android.app.smartspace.SmartspaceSession
import android.app.smartspace.SmartspaceTarget
import android.app.smartspace.SmartspaceTarget
import android.content.BroadcastReceiver
import android.content.BroadcastReceiver
import android.content.ContentProvider
import android.content.ContentResolver
import android.content.ContentResolver
import android.content.Context
import android.content.Context
import android.content.Intent
import android.content.Intent
@@ -700,10 +702,13 @@ class MediaDataManager(
            Log.d(TAG, "adding track for $userId from browser: $desc")
            Log.d(TAG, "adding track for $userId from browser: $desc")
        }
        }


        val currentEntry = mediaEntries.get(packageName)
        val appUid = currentEntry?.appUid ?: Process.INVALID_UID

        // Album art
        // Album art
        var artworkBitmap = desc.iconBitmap
        var artworkBitmap = desc.iconBitmap
        if (artworkBitmap == null && desc.iconUri != null) {
        if (artworkBitmap == null && desc.iconUri != null) {
            artworkBitmap = loadBitmapFromUri(desc.iconUri!!)
            artworkBitmap = loadBitmapFromUriForUser(desc.iconUri!!, userId, appUid, packageName)
        }
        }
        val artworkIcon =
        val artworkIcon =
            if (artworkBitmap != null) {
            if (artworkBitmap != null) {
@@ -712,9 +717,7 @@ class MediaDataManager(
                null
                null
            }
            }


        val currentEntry = mediaEntries.get(packageName)
        val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
        val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
        val appUid = currentEntry?.appUid ?: Process.INVALID_UID
        val isExplicit =
        val isExplicit =
            desc.extras?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
            desc.extras?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
@@ -1261,6 +1264,30 @@ class MediaDataManager(
            false
            false
        }
        }
    }
    }

    /** Returns a bitmap if the user can access the given URI, else null */
    private fun loadBitmapFromUriForUser(
        uri: Uri,
        userId: Int,
        appUid: Int,
        packageName: String,
    ): Bitmap? {
        try {
            val ugm = UriGrantsManager.getService()
            ugm.checkGrantUriPermission_ignoreNonSystem(
                appUid,
                packageName,
                ContentProvider.getUriWithoutUserId(uri),
                Intent.FLAG_GRANT_READ_URI_PERMISSION,
                ContentProvider.getUserIdFromUri(uri, userId)
            )
            return loadBitmapFromUri(uri)
        } catch (e: SecurityException) {
            Log.e(TAG, "Failed to get URI permission: $e")
        }
        return null
    }

    /**
    /**
     * Load a bitmap from a URI
     * Load a bitmap from a URI
     *
     *
+79 −0
Original line number Original line Diff line number Diff line
@@ -16,10 +16,12 @@


package com.android.systemui.media.controls.pipeline
package com.android.systemui.media.controls.pipeline


import android.app.IUriGrantsManager
import android.app.Notification
import android.app.Notification
import android.app.Notification.FLAG_NO_CLEAR
import android.app.Notification.FLAG_NO_CLEAR
import android.app.Notification.MediaStyle
import android.app.Notification.MediaStyle
import android.app.PendingIntent
import android.app.PendingIntent
import android.app.UriGrantsManager
import android.app.smartspace.SmartspaceAction
import android.app.smartspace.SmartspaceAction
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceManager
@@ -27,12 +29,14 @@ import android.app.smartspace.SmartspaceTarget
import android.content.Intent
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.graphics.drawable.Icon
import android.graphics.drawable.Icon
import android.media.MediaDescription
import android.media.MediaDescription
import android.media.MediaMetadata
import android.media.MediaMetadata
import android.media.session.MediaController
import android.media.session.MediaController
import android.media.session.MediaSession
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.media.session.PlaybackState
import android.net.Uri
import android.os.Bundle
import android.os.Bundle
import android.provider.Settings
import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.service.notification.StatusBarNotification
@@ -40,6 +44,7 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
import androidx.media.utils.MediaConstants
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.logging.InstanceId
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.InstanceIdSequenceFake
@@ -83,7 +88,9 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoSession
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoJUnit
import org.mockito.quality.Strictness


private const val KEY = "KEY"
private const val KEY = "KEY"
private const val KEY_2 = "KEY_2"
private const val KEY_2 = "KEY_2"
@@ -149,6 +156,8 @@ class MediaDataManagerTest : SysuiTestCase() {
    @Captor lateinit var stateCallbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
    @Captor lateinit var stateCallbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
    @Captor lateinit var sessionCallbackCaptor: ArgumentCaptor<(String) -> Unit>
    @Captor lateinit var sessionCallbackCaptor: ArgumentCaptor<(String) -> Unit>
    @Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig>
    @Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig>
    @Mock private lateinit var ugm: IUriGrantsManager
    @Mock private lateinit var imageSource: ImageDecoder.Source


    private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
    private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)


@@ -159,8 +168,17 @@ class MediaDataManagerTest : SysuiTestCase() {
            1
            1
        )
        )


    private lateinit var staticMockSession: MockitoSession

    @Before
    @Before
    fun setup() {
    fun setup() {
        staticMockSession =
            ExtendedMockito.mockitoSession()
                .mockStatic<UriGrantsManager>(UriGrantsManager::class.java)
                .mockStatic<ImageDecoder>(ImageDecoder::class.java)
                .strictness(Strictness.LENIENT)
                .startMocking()
        whenever(UriGrantsManager.getService()).thenReturn(ugm)
        foregroundExecutor = FakeExecutor(clock)
        foregroundExecutor = FakeExecutor(clock)
        backgroundExecutor = FakeExecutor(clock)
        backgroundExecutor = FakeExecutor(clock)
        uiExecutor = FakeExecutor(clock)
        uiExecutor = FakeExecutor(clock)
@@ -270,6 +288,7 @@ class MediaDataManagerTest : SysuiTestCase() {


    @After
    @After
    fun tearDown() {
    fun tearDown() {
        staticMockSession.finishMocking()
        session.release()
        session.release()
        mediaDataManager.destroy()
        mediaDataManager.destroy()
        Settings.Secure.putInt(
        Settings.Secure.putInt(
@@ -2198,6 +2217,66 @@ class MediaDataManagerTest : SysuiTestCase() {
        verify(listener).onMediaDataRemoved(eq(KEY))
        verify(listener).onMediaDataRemoved(eq(KEY))
    }
    }


    @Test
    fun testResumeMediaLoaded_hasArtPermission_artLoaded() {
        // When resume media is loaded and user/app has permission to access the art URI,
        whenever(
                ugm.checkGrantUriPermission_ignoreNonSystem(
                    anyInt(),
                    any(),
                    any(),
                    anyInt(),
                    anyInt()
                )
            )
            .thenReturn(1)
        val artwork = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
        val uri = Uri.parse("content://example")
        whenever(ImageDecoder.createSource(any(), eq(uri))).thenReturn(imageSource)
        whenever(ImageDecoder.decodeBitmap(any(), any())).thenReturn(artwork)

        val desc =
            MediaDescription.Builder().run {
                setTitle(SESSION_TITLE)
                setIconUri(uri)
                build()
            }
        addResumeControlAndLoad(desc)

        // Then the artwork is loaded
        assertThat(mediaDataCaptor.value.artwork).isNotNull()
    }

    @Test
    fun testResumeMediaLoaded_noArtPermission_noArtLoaded() {
        // When resume media is loaded and user/app does not have permission to access the art URI
        whenever(
                ugm.checkGrantUriPermission_ignoreNonSystem(
                    anyInt(),
                    any(),
                    any(),
                    anyInt(),
                    anyInt()
                )
            )
            .thenThrow(SecurityException("Test no permission"))
        val artwork = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
        val uri = Uri.parse("content://example")
        whenever(ImageDecoder.createSource(any(), eq(uri))).thenReturn(imageSource)
        whenever(ImageDecoder.decodeBitmap(any(), any())).thenReturn(artwork)

        val desc =
            MediaDescription.Builder().run {
                setTitle(SESSION_TITLE)
                setIconUri(uri)
                build()
            }
        addResumeControlAndLoad(desc)

        // Then the artwork is not loaded
        assertThat(mediaDataCaptor.value.artwork).isNull()
    }

    /** Helper function to add a basic media notification and capture the resulting MediaData */
    /** Helper function to add a basic media notification and capture the resulting MediaData */
    private fun addNotificationAndLoad() {
    private fun addNotificationAndLoad() {
        addNotificationAndLoad(mediaNotification)
        addNotificationAndLoad(mediaNotification)