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

Commit d0a4db8c authored by Yein Jo's avatar Yein Jo
Browse files

Fix FrameBuffer and build.gradle to work with system sdk.

Now MagicPortrait requires the custom system SDK, it's easier for us to
build WeatherLib with the same setup (same gradle version + have
namespace) to avoid SDK discrepency.

FrameBuffer is also updated such that it no longer triggers the image
ready callback when it's closed.

Bug: 330905324
Test: m, gradle, FrameBufferTest
Flag: NA
Change-Id: I7682ce3abadf223197fa4900ae1f770f683fa7a7
parent d20f12ea
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -13,7 +13,7 @@
// limitations under the License.
buildscript {
    ext.versions = [
            'gradle'       : '7.4.2',
            'gradle'       : '8.1.0',
            'minSdk'       : 34,
            'targetSdk'    : 34,
            'compileSdk'   : 34,
@@ -56,6 +56,7 @@ apply plugin: 'kotlin-android'
apply plugin: 'org.jetbrains.kotlin.kapt'

android {
    namespace 'com.google.android.wallpaper.weathereffects'
    compileSdk versions.compileSdk

    defaultConfig {
+23 −0
Original line number Diff line number Diff line
@@ -19,6 +19,11 @@ android {
            java.srcDirs = ["${rootDir}/graphics/src"]
            assets.srcDirs = ["${rootDir}/graphics/assets"]
        }

        androidTest {
            java.srcDirs = ["${rootDir}/graphics/tests/src"]
            res.srcDirs = ["${rootDir}/graphics/tests/res"]
        }
    }

    buildTypes {
@@ -42,10 +47,28 @@ android {
    kotlinOptions {
        jvmTarget = '17'
    }

    testOptions {
        unitTests {
            includeAndroidResources = true
        }
    }
}

dependencies {
    implementation 'androidx.core:core-ktx:1.12.0'
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.11.0'

    androidTestImplementation "junit:junit:4.13.2"
    androidTestImplementation "androidx.test:core:1.5.0"
    androidTestImplementation "androidx.test:rules:1.5.0"
    androidTestImplementation "androidx.test:runner:1.5.2"
    androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5'
    androidTestImplementation "com.google.truth:truth:1.1.3"
    androidTestImplementation "org.mockito:mockito-core:5.10.0"
    androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito-inline:2.28.3"
    androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito-inline-extended:2.28.3"
    androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4"
    androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4"
}
+12 −4
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.graphics.RecordingCanvas
import android.graphics.RenderEffect
import android.graphics.RenderNode
import android.hardware.HardwareBuffer
import androidx.annotation.VisibleForTesting
import java.time.Duration
import java.util.concurrent.Executor
import java.util.concurrent.Executors
@@ -49,7 +50,7 @@ class FrameBuffer(width: Int, height: Int, format: Int = HardwareBuffer.RGBA_888
        }

    private val executor = Executors.newFixedThreadPool(/* nThreads = */ 1)
    private val colorSpace = ColorSpace.get(ColorSpace.Named.SRGB)
    @VisibleForTesting val colorSpace = ColorSpace.get(ColorSpace.Named.SRGB)

    /**
     * Recording drawing commands.
@@ -81,15 +82,18 @@ class FrameBuffer(width: Int, height: Int, format: Int = HardwareBuffer.RGBA_888
     *   executor.
     */
    fun tryObtainingImage(onImageReady: (image: Bitmap) -> Unit, callbackExecutor: Executor) {
        if (renderer.isClosed) return
        renderer.obtainRenderRequest().setColorSpace(colorSpace).draw(executor) { result ->
            if (result.status == HardwareBufferRenderer.RenderResult.SUCCESS) {
                result.fence.await(Duration.ofMillis(3000))
                result.fence.await(Duration.ofMillis(RESULT_FENCE_TIME_OUT))
                if (!buffer.isClosed) {
                    Bitmap.wrapHardwareBuffer(buffer, colorSpace)?.let {
                        callbackExecutor.execute { onImageReady.invoke(it) }
                    }
                }
            }
        }
    }

    /**
     * Configure the [FrameBuffer] to apply to this RenderNode. This will apply a visual effect to
@@ -99,4 +103,8 @@ class FrameBuffer(width: Int, height: Int, format: Int = HardwareBuffer.RGBA_888
     *   configured RenderEffects.
     */
    fun setRenderEffect(renderEffect: RenderEffect?) = node.setRenderEffect(renderEffect)

    companion object {
        const val RESULT_FENCE_TIME_OUT = 3000L
    }
}
+92 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.wallpaper.weathereffects.graphics

import android.graphics.Bitmap
import android.graphics.Color
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.android.wallpaper.weathereffects.graphics.FrameBuffer.Companion.RESULT_FENCE_TIME_OUT
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class FrameBufferTest {

    private val executor = MoreExecutors.directExecutor()

    @Test
    fun onImageReady_invokesCallback() {
        val expectedWidth = 1
        val expectedHeight = 1
        val expectedColor = Color.RED

        val buffer = FrameBuffer(expectedWidth, expectedHeight)
        buffer.beginDrawing().drawColor(expectedColor)
        buffer.endDrawing()

        val latch = CountDownLatch(1)
        var bitmap: Bitmap? = null

        buffer.tryObtainingImage(
            {
                bitmap = it
                latch.countDown()
            },
            executor
        )

        assertThat(latch.await(RESULT_FENCE_TIME_OUT, TimeUnit.MILLISECONDS)).isTrue()

        assertThat(bitmap).isNotNull()
        val resultBitmap = bitmap!!
        assertThat(resultBitmap.width).isEqualTo(expectedWidth)
        assertThat(resultBitmap.height).isEqualTo(expectedHeight)
        assertThat(resultBitmap.colorSpace).isEqualTo(buffer.colorSpace)

        // Color sampling only works on software bitmap.
        val softwareBitmap = resultBitmap.copy(Bitmap.Config.ARGB_8888, false)
        assertThat(softwareBitmap.getPixel(0, 0)).isEqualTo(expectedColor)
    }

    @Test
    fun close_onImageReady_doesNotInvokeCallback() {
        val buffer = FrameBuffer(width = 1, height = 1)
        buffer.beginDrawing().drawColor(Color.RED)
        buffer.endDrawing()

        // Call close before we obtain image.
        buffer.close()

        val latch = CountDownLatch(1)
        var bitmap: Bitmap? = null

        buffer.tryObtainingImage(
            {
                bitmap = it
                latch.countDown()
            },
            executor
        )

        assertThat(latch.await(RESULT_FENCE_TIME_OUT, TimeUnit.MILLISECONDS)).isFalse()
        assertThat(bitmap).isNull()
    }
}