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

Commit 285d9730 authored by Hawkwood Glazier's avatar Hawkwood Glazier
Browse files

Update PluginManager to kotlin

Bug: 439580338
Flag: NONE Conversion to Kotlin
Test: Tested clocks integration with plugins
Change-Id: I6d5cf927634344e67c302c790da79e1f0ebb5b98
parent 75720fac
Loading
Loading
Loading
Loading
+7 −20
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestableContext
import com.android.systemui.plugins.Plugin
import com.android.systemui.plugins.PluginListener
import com.android.systemui.plugins.PluginManager
import com.android.systemui.plugins.annotations.Requires
import com.android.systemui.shared.plugins.PluginEnabler.DisableReason
import com.android.systemui.util.concurrency.FakeExecutor
@@ -72,7 +73,7 @@ class PluginActionManagerTest : SysuiTestCase() {
            PluginInstance.Factory(
                VersionCheckerImpl(),
                this::class.java.classLoader!!,
                emptyList(),
                PluginManager.Config(),
                BuildInfo(BuildVariant.User, isDebuggable = false),
            ) {
            override fun <T : Plugin> create(
@@ -103,7 +104,7 @@ class PluginActionManagerTest : SysuiTestCase() {
                mFakeExecutor,
                mNotificationManager,
                mMockEnabler,
                ArrayList(),
                PluginManager.Config(),
                mPluginInstanceFactory,
            )

@@ -113,7 +114,6 @@ class PluginActionManagerTest : SysuiTestCase() {
                mMockListener,
                TestPlugin::class.java,
                allowMultiple = true,
                isDebuggable = true,
            )
        whenever(mMockPlugin.version).thenReturn(1)
    }
@@ -175,7 +175,6 @@ class PluginActionManagerTest : SysuiTestCase() {
                mMockListener,
                TestPlugin::class.java,
                allowMultiple = true,
                isDebuggable = false,
            )
        setupFakePmQuery()

@@ -199,17 +198,11 @@ class PluginActionManagerTest : SysuiTestCase() {
                mFakeExecutor,
                mNotificationManager,
                mMockEnabler,
                listOf(PRIVILEGED_PACKAGE),
                PluginManager.Config(listOf(PRIVILEGED_PACKAGE)),
                mPluginInstanceFactory,
            )
        mPluginActionManager =
            factory.create(
                "myAction",
                mMockListener,
                TestPlugin::class.java,
                allowMultiple = true,
                isDebuggable = false,
            )
            factory.create("myAction", mMockListener, TestPlugin::class.java, allowMultiple = true)
        setupFakePmQuery()

        mPluginActionManager.loadAll()
@@ -259,17 +252,11 @@ class PluginActionManagerTest : SysuiTestCase() {
                mFakeExecutor,
                mNotificationManager,
                mMockEnabler,
                listOf(PRIVILEGED_PACKAGE),
                PluginManager.Config(listOf(PRIVILEGED_PACKAGE)),
                mPluginInstanceFactory,
            )
        mPluginActionManager =
            factory.create(
                "myAction",
                mMockListener,
                TestPlugin::class.java,
                allowMultiple = true,
                isDebuggable = false,
            )
            factory.create("myAction", mMockListener, TestPlugin::class.java, allowMultiple = true)

        createPlugin() // Get into valid created state.

+2 −1
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.Plugin
import com.android.systemui.plugins.PluginLifecycleManager
import com.android.systemui.plugins.PluginListener
import com.android.systemui.plugins.PluginManager
import com.android.systemui.plugins.PluginWrapper
import com.android.systemui.plugins.TestPlugin
import com.android.systemui.plugins.annotations.Requires
@@ -96,7 +97,7 @@ class PluginInstanceTest : SysuiTestCase() {
            PluginInstance.Factory(
                mVersionChecker,
                javaClass.classLoader!!,
                listOf(PRIVILEGED_PACKAGE),
                PluginManager.Config(listOf(PRIVILEGED_PACKAGE)),
                BuildInfo(BuildVariant.User, isDebuggable = false),
            ) { _ ->
                val plugin = TestPluginImpl(mCounter)
+0 −197
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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.shared.plugins;

import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.testing.TestableLooper.RunWithLooper;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.annotations.ProvidesInterface;
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@SmallTest
@RunWith(AndroidJUnit4.class)
@RunWithLooper
public class PluginManagerTest extends SysuiTestCase {
    private static final String PRIVILEGED_PACKAGE = "com.android.systemui";

    @Mock PluginActionManager.Factory mMockFactory;
    @Mock PluginActionManager<TestPlugin> mMockPluginInstance;
    @Mock PluginListener<TestPlugin> mMockListener;
    @Mock PackageManager mMockPackageManager;
    @Mock PluginEnabler mMockPluginEnabler;
    @Mock PluginPrefs mMockPluginPrefs;
    @Mock UncaughtExceptionPreHandlerManager mMockExPreHandlerManager;

    @Captor ArgumentCaptor<UncaughtExceptionHandler> mExPreHandlerCaptor;

    private PluginManagerImpl mPluginManager;
    private UncaughtExceptionHandler mPluginExceptionHandler;

    @Before
    public void setup() throws Exception {
        MockitoAnnotations.initMocks(this);
        when(mMockFactory.create(any(), any(), eq(TestPlugin.class), anyBoolean(), anyBoolean()))
                .thenReturn(mMockPluginInstance);

        mPluginManager = new PluginManagerImpl(
                getContext(), mMockFactory, true,
                mMockExPreHandlerManager, mMockPluginEnabler,
                mMockPluginPrefs, new ArrayList<>());
        captureExceptionHandler();
    }

    @Test
    public void testAddListener() {
        mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);

        verify(mMockPluginInstance).loadAll();
    }

    @Test
    public void testRemoveListener() {
        mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);

        mPluginManager.removePluginListener(mMockListener);
        verify(mMockPluginInstance).destroy();
    }

    @Test
    @RunWithLooper(setAsMainLooper = true)
    public void testNonDebuggable_nonPrivileged() {
        mPluginManager = new PluginManagerImpl(
                getContext(), mMockFactory, false,
                mMockExPreHandlerManager, mMockPluginEnabler,
                mMockPluginPrefs, new ArrayList<>());
        captureExceptionHandler();

        String sourceDir = "myPlugin";
        ApplicationInfo applicationInfo = new ApplicationInfo();
        applicationInfo.sourceDir = sourceDir;
        applicationInfo.packageName = PRIVILEGED_PACKAGE;
        mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
        verify(mMockFactory).create(eq("myAction"), eq(mMockListener), eq(TestPlugin.class),
                eq(false), eq(false));
        verify(mMockPluginInstance).loadAll();
    }

    @Test
    @RunWithLooper(setAsMainLooper = true)
    public void testNonDebuggable_privilegedPackage() {
        mPluginManager = new PluginManagerImpl(
                getContext(), mMockFactory, false,
                mMockExPreHandlerManager, mMockPluginEnabler,
                mMockPluginPrefs, Collections.singletonList(PRIVILEGED_PACKAGE));
        captureExceptionHandler();

        String sourceDir = "myPlugin";
        ApplicationInfo privilegedApplicationInfo = new ApplicationInfo();
        privilegedApplicationInfo.sourceDir = sourceDir;
        privilegedApplicationInfo.packageName = PRIVILEGED_PACKAGE;
        ApplicationInfo invalidApplicationInfo = new ApplicationInfo();
        invalidApplicationInfo.sourceDir = sourceDir;
        invalidApplicationInfo.packageName = "com.android.invalidpackage";
        mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
        verify(mMockFactory).create(eq("myAction"), eq(mMockListener), eq(TestPlugin.class),
                eq(false), eq(false));
        verify(mMockPluginInstance).loadAll();
    }

    @Test
    public void testExceptionHandler_foundPlugin() {
        mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
        when(mMockPluginInstance.checkAndDisable(any())).thenReturn(true);

        mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable());

        verify(mMockPluginInstance, Mockito.atLeastOnce()).checkAndDisable(
                ArgumentCaptor.forClass(String.class).capture());
        verify(mMockPluginInstance, Mockito.never()).disableAll();
    }

    @Test
    public void testExceptionHandler_noFoundPlugin() {
        mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
        when(mMockPluginInstance.checkAndDisable(any())).thenReturn(false);

        mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable());

        verify(mMockPluginInstance, Mockito.atLeastOnce()).checkAndDisable(
                ArgumentCaptor.forClass(String.class).capture());
        verify(mMockPluginInstance).disableAll();
    }

    @Test
    public void testDisableIntent() {
        NotificationManager nm = mock(NotificationManager.class);
        mContext.addMockSystemService(Context.NOTIFICATION_SERVICE, nm);
        mContext.setMockPackageManager(mMockPackageManager);

        ComponentName testComponent = new ComponentName(getContext().getPackageName(),
                PluginManagerTest.class.getName());
        Intent intent = new Intent(PluginManagerImpl.DISABLE_PLUGIN);
        intent.setData(Uri.parse("package://" + testComponent.flattenToString()));
        mPluginManager.onReceive(mContext, intent);
        verify(nm).cancel(eq(testComponent.getClassName()), eq(SystemMessage.NOTE_PLUGIN));
        verify(mMockPluginEnabler).setDisabled(testComponent,
                PluginEnabler.DisableReason.DISABLED_INVALID_VERSION);
    }

    private void captureExceptionHandler() {
        verify(mMockExPreHandlerManager, atLeastOnce()).registerHandler(
                mExPreHandlerCaptor.capture());
        List<UncaughtExceptionHandler> allValues = mExPreHandlerCaptor.getAllValues();
        mPluginExceptionHandler = allValues.get(allValues.size() - 1);
    }

    @ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
    public interface TestPlugin extends Plugin {
        String ACTION = "testAction";
        int VERSION = 1;
    }
}
+210 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.shared.plugins

import android.app.NotificationManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.net.Uri
import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.messages.nano.SystemMessageProto
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.Plugin
import com.android.systemui.plugins.PluginListener
import com.android.systemui.plugins.PluginManager
import com.android.systemui.plugins.annotations.ProvidesInterface
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
import java.lang.Thread.UncaughtExceptionHandler
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock

@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper
class PluginManagerTest : SysuiTestCase() {
    @Mock lateinit var mMockFactory: PluginActionManager.Factory
    @Mock lateinit var mMockPluginInstance: PluginActionManager<TestPlugin>
    @Mock lateinit var mMockListener: PluginListener<TestPlugin>
    @Mock lateinit var mMockPackageManager: PackageManager
    @Mock lateinit var mMockPluginEnabler: PluginEnabler
    @Mock lateinit var mMockPluginPrefs: PluginPrefs
    @Mock lateinit var mMockExPreHandlerManager: UncaughtExceptionPreHandlerManager

    private lateinit var mPluginManager: PluginManagerImpl
    private lateinit var mPluginExceptionHandler: UncaughtExceptionHandler

    @Before
    @Throws(Exception::class)
    fun setup() {
        MockitoAnnotations.openMocks(this)
        whenever(mMockFactory.create(anyString(), any(), eq(TestPlugin::class.java), anyBoolean()))
            .thenReturn(mMockPluginInstance)

        mPluginManager =
            PluginManagerImpl(
                context,
                mMockFactory,
                mMockExPreHandlerManager,
                mMockPluginEnabler,
                mMockPluginPrefs,
                PluginManager.Config(),
            )
        captureExceptionHandler()
    }

    @Test
    fun testAddListener() {
        mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin::class.java)

        verify(mMockPluginInstance).loadAll()
    }

    @Test
    fun testRemoveListener() {
        mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin::class.java)
        mPluginManager.removePluginListener(mMockListener)

        verify(mMockPluginInstance).destroy()
    }

    @Test
    @RunWithLooper(setAsMainLooper = true)
    fun testNonDebuggable_nonPrivileged() {
        mPluginManager =
            PluginManagerImpl(
                context,
                mMockFactory,
                mMockExPreHandlerManager,
                mMockPluginEnabler,
                mMockPluginPrefs,
                PluginManager.Config(),
            )
        captureExceptionHandler()

        val sourceDir = "myPlugin"
        val applicationInfo = ApplicationInfo()
        applicationInfo.sourceDir = sourceDir
        applicationInfo.packageName = PRIVILEGED_PACKAGE
        mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin::class.java)

        verify(mMockFactory)
            .create(eq("myAction"), eq(mMockListener), eq(TestPlugin::class.java), eq(false))
        verify(mMockPluginInstance).loadAll()
    }

    @Test
    @RunWithLooper(setAsMainLooper = true)
    fun testNonDebuggable_privilegedPackage() {
        mPluginManager =
            PluginManagerImpl(
                context,
                mMockFactory,
                mMockExPreHandlerManager,
                mMockPluginEnabler,
                mMockPluginPrefs,
                PluginManager.Config(listOf(PRIVILEGED_PACKAGE)),
            )
        captureExceptionHandler()

        val sourceDir = "myPlugin"
        val privilegedApplicationInfo = ApplicationInfo()
        privilegedApplicationInfo.sourceDir = sourceDir
        privilegedApplicationInfo.packageName = PRIVILEGED_PACKAGE
        val invalidApplicationInfo = ApplicationInfo()
        invalidApplicationInfo.sourceDir = sourceDir
        invalidApplicationInfo.packageName = "com.android.invalidpackage"
        mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin::class.java)

        verify(mMockFactory)
            .create(eq("myAction"), eq(mMockListener), eq(TestPlugin::class.java), eq(false))
        verify(mMockPluginInstance).loadAll()
    }

    @Test
    fun testExceptionHandler_foundPlugin() {
        mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin::class.java)
        whenever(mMockPluginInstance.checkAndDisable(any())).thenReturn(true)

        mPluginExceptionHandler.uncaughtException(Thread.currentThread(), Throwable())

        verify(mMockPluginInstance, atLeastOnce())
            .checkAndDisable(argumentCaptor<String>().capture())
        verify(mMockPluginInstance, never()).disableAll()
    }

    @Test
    fun testExceptionHandler_noFoundPlugin() {
        mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin::class.java)
        whenever(mMockPluginInstance.checkAndDisable(any())).thenReturn(false)

        mPluginExceptionHandler.uncaughtException(Thread.currentThread(), Throwable())

        verify(mMockPluginInstance, atLeastOnce())
            .checkAndDisable(argumentCaptor<String>().capture())
        verify(mMockPluginInstance).disableAll()
    }

    @Test
    fun testDisableIntent() {
        val nm = mock<NotificationManager>()
        mContext.addMockSystemService(Context.NOTIFICATION_SERVICE, nm)
        mContext.setMockPackageManager(mMockPackageManager)

        val testComponent = ComponentName(context.packageName, PluginManagerTest::class.java.name)
        val intent = Intent(PluginManagerImpl.DISABLE_PLUGIN)
        intent.setData(Uri.parse("package://" + testComponent.flattenToString()))
        mPluginManager.onReceive(mContext, intent)

        verify(nm)
            .cancel(eq(testComponent.className), eq(SystemMessageProto.SystemMessage.NOTE_PLUGIN))
        verify(mMockPluginEnabler)
            .setDisabled(testComponent, PluginEnabler.DisableReason.DISABLED_INVALID_VERSION)
    }

    private fun captureExceptionHandler() {
        val captor = argumentCaptor<UncaughtExceptionHandler>()
        verify(mMockExPreHandlerManager, atLeastOnce()).registerHandler(captor.capture())
        mPluginExceptionHandler = captor.lastValue
    }

    @ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
    interface TestPlugin : Plugin {
        companion object {
            const val ACTION: String = "testAction"
            const val VERSION: Int = 1
        }
    }

    companion object {
        private const val PRIVILEGED_PACKAGE = "com.android.systemui"
    }
}
+5 −4
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.systemui.statusbar.policy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -83,8 +84,8 @@ public class ExtensionControllerImplTest extends SysuiTestCase {
                .withPlugin(OverlayPlugin.class)
                .build();
        ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class);
        verify(mPluginManager).addPluginListener(eq(OverlayPlugin.ACTION), listener.capture(),
                eq(OverlayPlugin.class));
        verify(mPluginManager).addPluginListener(listener.capture(),
                eq(OverlayPlugin.class), eq(false));

        listener.getValue().onPluginConnected(plugin, null);
        assertEquals(plugin, ext.get());
@@ -163,7 +164,7 @@ public class ExtensionControllerImplTest extends SysuiTestCase {
                .withDefault(() -> def)
                .withUiMode(Configuration.UI_MODE_TYPE_CAR, () -> uiMode)
                .withTunerFactory(factory)
                .withPlugin(Object.class, "some_action")
                .withPlugin(Object.class)
                .build();

        // Test default first.
@@ -188,7 +189,7 @@ public class ExtensionControllerImplTest extends SysuiTestCase {

        // Lastly, check a plugin.
        ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class);
        verify(mPluginManager).addPluginListener(any(), listener.capture(), any());
        verify(mPluginManager).addPluginListener(listener.capture(), any(), anyBoolean());
        listener.getValue().onPluginConnected(plugin, null);
        assertEquals(plugin, ext.get());
    }
Loading