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

Commit c2254172 authored by Hawkwood Glazier's avatar Hawkwood Glazier
Browse files

Initial ClockPlugin interfaces and ClockRegistry functions

Bug: 229771520
Test: Automated
Change-Id: I2bc8be56f9fb91734aa7d430270c3e9ad7328ed0
parent 552b0fc8
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -23,7 +23,9 @@ import java.util.TimeZone;

/**
 * Plugin used to replace main clock in keyguard.
 * @deprecated Migrating to ClockProviderPlugin
 */
@Deprecated
@ProvidesInterface(action = ClockPlugin.ACTION, version = ClockPlugin.VERSION)
public interface ClockPlugin extends Plugin {

+64 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.android.systemui.plugins

import com.android.systemui.plugins.annotations.ProvidesInterface
import android.annotation.FloatRange
import android.graphics.drawable.Drawable
import android.view.View

/** Identifies a clock design */
typealias ClockId = String

/** A Plugin which exposes the ClockProvider interface */
@ProvidesInterface(action = ClockProviderPlugin.ACTION, version = ClockProviderPlugin.VERSION)
interface ClockProviderPlugin : Plugin, ClockProvider {
    companion object {
        const val ACTION = "com.android.systemui.action.PLUGIN_CLOCK_PROVIDER"
        const val VERSION = 1
    }
}

/** Interface for building clocks and providing information about those clocks */
interface ClockProvider {
    /** Returns metadata for all clocks this provider knows about */
    fun getClocks(): List<ClockMetadata>

    /** Initializes and returns the target clock design */
    fun createClock(id: ClockId): Clock

    /** A static thumbnail for rendering in some examples */
    fun getClockThumbnail(id: ClockId): Drawable?
}

/** Interface for controlling an active clock */
interface Clock {
    /** A small version of the clock, appropriate for smaller viewports */
    val smallClock: View

    /** A large version of the clock, appropriate when a bigger viewport is available */
    val largeClock: View

    /** Callback to update the clock view to the current time */
    fun onTimeTick()

    /** Sets the level of the AOD transition */
    fun setAodFraction(@FloatRange(from = 0.0, to = 1.0) fraction: Float)
}

/** Some data about a clock design */
data class ClockMetadata(
    val clockId: ClockId,
    val name: String
)
 No newline at end of file
+3 −0
Original line number Diff line number Diff line
@@ -53,8 +53,11 @@ import javax.inject.Inject;

/**
 * Manages custom clock faces for AOD and lock screen.
 *
 * @deprecated Migrate to ClockRegistry
 */
@SysUISingleton
@Deprecated
public final class ClockManager {

    private static final String TAG = "ClockOptsProvider";
+102 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.android.keyguard.clock

import android.content.Context
import android.graphics.drawable.Drawable
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.Clock
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProvider
import com.android.systemui.plugins.ClockProviderPlugin
import com.android.systemui.plugins.PluginListener
import com.android.systemui.shared.plugins.PluginManager
import javax.inject.Inject

private val TAG = ClockRegistry::class.simpleName
private const val DEFAULT_CLOCK_ID = "DEFAULT"

typealias ClockChangeListener = () -> Unit

/** ClockRegistry aggregates providers and plugins */
// TODO: Is this the right place for this?
@SysUISingleton
class ClockRegistry @Inject constructor(
    val context: Context,
    val pluginManager: PluginManager
) {
    val pluginListener = object : PluginListener<ClockProviderPlugin> {
        override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) {
            for (clock in plugin.getClocks()) {
                val id = clock.clockId
                val current = availableClocks[id]
                if (current != null) {
                    Log.e(TAG, "Clock Id conflict: $id is registered by both " +
                            "${plugin::class.simpleName} and ${current.provider::class.simpleName}")
                    return
                }

                availableClocks[id] = ClockInfo(clock, plugin)
            }
        }

        override fun onPluginDisconnected(plugin: ClockProviderPlugin) {
            for (clock in plugin.getClocks()) {
                availableClocks.remove(clock.clockId)
            }
        }
    }

    private val availableClocks = mutableMapOf<ClockId, ClockInfo>()

    init {
        pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java)
        // TODO: Register Settings ContentObserver
    }

    fun getClocks(): List<ClockMetadata> = availableClocks.map { (_, clock) -> clock.metadata }

    fun getClockThumbnail(clockId: ClockId): Drawable? =
        availableClocks[clockId]?.provider?.getClockThumbnail(clockId)

    fun createExampleClock(clockId: ClockId): Clock? = createClock(clockId)

    fun getCurrentClock(): Clock {
        val clockId = "" // TODO: Load setting
        if (!clockId.isNullOrEmpty()) {
            val clock = createClock(clockId)
            if (clock != null) {
                return clock
            } else {
                Log.e(TAG, "Clock $clockId not found; using default")
            }
        }

        return createClock(DEFAULT_CLOCK_ID)!!
    }

    private fun createClock(clockId: ClockId): Clock? =
        availableClocks[clockId]?.provider?.createClock(clockId)

    fun setCurrentClock(clockId: ClockId) {
        // TODO: Write Setting
    }

    private data class ClockInfo(
        val metadata: ClockMetadata,
        val provider: ClockProvider
    )
}
 No newline at end of file
+138 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.android.keyguard.clock

import android.content.Context
import android.graphics.drawable.Drawable
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.Clock
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProviderPlugin
import com.android.systemui.plugins.PluginListener
import com.android.systemui.shared.plugins.PluginManager
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import junit.framework.Assert.assertEquals
import junit.framework.Assert.fail
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit

@RunWith(AndroidTestingRunner::class)
@SmallTest
class ClockRegistryTest : SysuiTestCase() {

    @JvmField @Rule val mockito = MockitoJUnit.rule()
    @Mock private lateinit var mockContext: Context
    @Mock private lateinit var mockPluginManager: PluginManager
    @Mock private lateinit var mockClock: Clock
    @Mock private lateinit var mockThumbnail: Drawable
    private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
    private lateinit var registry: ClockRegistry

    private fun failFactory(): Clock {
        fail("Unexpected call to createClock")
        return null!!
    }

    private fun failThumbnail(): Drawable? {
        fail("Unexpected call to getThumbnail")
        return null
    }

    private class FakeClockPlugin : ClockProviderPlugin {
        private val metadata = mutableListOf<ClockMetadata>()
        private val createCallbacks = mutableMapOf<ClockId, () -> Clock>()
        private val thumbnailCallbacks = mutableMapOf<ClockId, () -> Drawable?>()

        override fun getClocks() = metadata
        override fun createClock(id: ClockId): Clock = createCallbacks[id]!!()
        override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!()

        fun addClock(
            id: ClockId,
            name: String,
            create: () -> Clock,
            getThumbnail: () -> Drawable?
        ) {
            metadata.add(ClockMetadata(id, name))
            createCallbacks[id] = create
            thumbnailCallbacks[id] = getThumbnail
        }
    }

    @Before
    fun setUp() {
        val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
        registry = ClockRegistry(mockContext, mockPluginManager)
        verify(mockPluginManager).addPluginListener(captor.capture(),
            eq(ClockProviderPlugin::class.java))
        pluginListener = captor.value
    }

    @Test
    fun pluginRegistration_CorrectState() {
        val plugin1 = FakeClockPlugin()
        plugin1.addClock("clock_1", "clock 1", ::failFactory, ::failThumbnail)
        plugin1.addClock("clock_2", "clock 2", ::failFactory, ::failThumbnail)

        val plugin2 = FakeClockPlugin()
        plugin2.addClock("clock_3", "clock 3", ::failFactory, ::failThumbnail)
        plugin2.addClock("clock_4", "clock 4", ::failFactory, ::failThumbnail)

        pluginListener.onPluginConnected(plugin1, mockContext)
        pluginListener.onPluginConnected(plugin2, mockContext)
        val list = registry.getClocks()
        assertEquals(list, listOf(
            ClockMetadata("clock_1", "clock 1"),
            ClockMetadata("clock_2", "clock 2"),
            ClockMetadata("clock_3", "clock 3"),
            ClockMetadata("clock_4", "clock 4")
        ))
    }

    @Test
    fun clockIdConflict_ErrorWithoutCrash() {
        val plugin1 = FakeClockPlugin()
        plugin1.addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail })
        plugin1.addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail })

        val plugin2 = FakeClockPlugin()
        plugin2.addClock("clock_1", "clock 1", ::failFactory, ::failThumbnail)
        plugin2.addClock("clock_2", "clock 2", ::failFactory, ::failThumbnail)

        pluginListener.onPluginConnected(plugin1, mockContext)
        pluginListener.onPluginConnected(plugin2, mockContext)
        val list = registry.getClocks()
        assertEquals(list, listOf(
            ClockMetadata("clock_1", "clock 1"),
            ClockMetadata("clock_2", "clock 2")
        ))

        assertEquals(registry.createExampleClock("clock_1"), mockClock)
        assertEquals(registry.createExampleClock("clock_2"), mockClock)
        assertEquals(registry.getClockThumbnail("clock_1"), mockThumbnail)
        assertEquals(registry.getClockThumbnail("clock_2"), mockThumbnail)
    }
}