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

Commit b11f5c21 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Update PluginManager to kotlin" into main

parents b474b520 285d9730
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