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

Commit 220c4155 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add notifications for incorrect plugin versions"

parents 2b3fffbb 26bc8996
Loading
Loading
Loading
Loading
+49 −3
Original line number Diff line number Diff line
@@ -14,18 +14,27 @@

package com.android.systemui.plugins;

import android.app.Notification;
import android.app.Notification.Action;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.UserHandle;
import android.util.Log;
import android.view.LayoutInflater;

@@ -260,10 +269,9 @@ public class PluginInstanceManager<T extends Plugin> {
            String pkg = component.getPackageName();
            String cls = component.getClassName();
            try {
                PackageManager pm = mPm;
                ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
                ApplicationInfo info = mPm.getApplicationInfo(pkg, 0);
                // TODO: This probably isn't needed given that we don't have IGNORE_SECURITY on
                if (pm.checkPermission(PLUGIN_PERMISSION, pkg)
                if (mPm.checkPermission(PLUGIN_PERMISSION, pkg)
                        != PackageManager.PERMISSION_GRANTED) {
                    Log.d(TAG, "Plugin doesn't have permission: " + pkg);
                    return null;
@@ -275,6 +283,44 @@ public class PluginInstanceManager<T extends Plugin> {
                Class<?> pluginClass = Class.forName(cls, true, classLoader);
                T plugin = (T) pluginClass.newInstance();
                if (plugin.getVersion() != mVersion) {
                    final int id = mContext.getResources().getIdentifier("notification_plugin",
                            "id", mContext.getPackageName());
                    final int icon = mContext.getResources().getIdentifier("tuner", "drawable",
                            mContext.getPackageName());
                    final int color = Resources.getSystem().getIdentifier(
                            "system_notification_accent_color", "color", "android");
                    final Notification.Builder nb = new Notification.Builder(mContext)
                            .setStyle(new Notification.BigTextStyle())
                            .setSmallIcon(icon)
                            .setWhen(0)
                            .setShowWhen(false)
                            .setPriority(Notification.PRIORITY_MAX)
                            .setVisibility(Notification.VISIBILITY_PUBLIC)
                            .setColor(mContext.getColor(color));
                    String label = cls;
                    try {
                        label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString();
                    } catch (NameNotFoundException e) {
                    }
                    if (plugin.getVersion() < mVersion) {
                        // Localization not required as this will never ever appear in a user build.
                        nb.setContentTitle("Plugin \"" + label + "\" is too old")
                                .setContentText("Contact plugin developer to get an updated"
                                        + " version.\nPlugin version: " + plugin.getVersion()
                                        + "\nSystem version: " + mVersion);
                    } else {
                        // Localization not required as this will never ever appear in a user build.
                        nb.setContentTitle("Plugin \"" + label + "\" is too new")
                                .setContentText("Check to see if an OTA is available.\n"
                                        + "Plugin version: " + plugin.getVersion()
                                        + "\nSystem version: " + mVersion);
                    }
                    Intent i = new Intent(PluginManager.DISABLE_PLUGIN).setData(
                            Uri.parse("package://" + component.flattenToString()));
                    PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0);
                    nb.addAction(new Action.Builder(null, "Disable plugin", pi).build());
                    mContext.getSystemService(NotificationManager.class)
                            .notifyAsUser(cls, id, nb.build(), UserHandle.ALL);
                    // TODO: Warn user.
                    Log.w(TAG, "Plugin has invalid interface version " + plugin.getVersion()
                            + ", expected " + mVersion);
+17 −0
Original line number Diff line number Diff line
@@ -14,11 +14,14 @@

package com.android.systemui.plugins;

import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Build;
@@ -42,6 +45,8 @@ public class PluginManager extends BroadcastReceiver {

    public static final String PLUGIN_CHANGED = "com.android.systemui.action.PLUGIN_CHANGED";

    static final String DISABLE_PLUGIN = "com.android.systemui.action.DISABLE_PLUGIN";

    private static PluginManager sInstance;

    private final HandlerThread mBackgroundThread;
@@ -112,6 +117,7 @@ public class PluginManager extends BroadcastReceiver {
        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        filter.addAction(PLUGIN_CHANGED);
        filter.addAction(DISABLE_PLUGIN);
        filter.addDataScheme("package");
        mContext.registerReceiver(this, filter);
        filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
@@ -128,6 +134,17 @@ public class PluginManager extends BroadcastReceiver {
            for (PluginInstanceManager manager : mPluginMap.values()) {
                manager.loadAll();
            }
        } else if (DISABLE_PLUGIN.equals(intent.getAction())) {
            Uri uri = intent.getData();
            ComponentName component = ComponentName.unflattenFromString(
                    uri.toString().substring(10));
            mContext.getPackageManager().setComponentEnabledSetting(component,
                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                    PackageManager.DONT_KILL_APP);
            int id = mContext.getResources().getIdentifier("notification_plugin", "id",
                    mContext.getPackageName());
            mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(),
                    id);
        } else {
            Uri data = intent.getData();
            String pkg = data.getEncodedSchemeSpecificPart();
+1 −0
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@
    <item type="id" name="notification_hidden"/>
    <item type="id" name="notification_volumeui"/>
    <item type="id" name="notification_temperature"/>
    <item type="id" name="notification_plugin"/>
    <item type="id" name="transformation_start_x_tag"/>
    <item type="id" name="transformation_start_y_tag"/>
    <item type="id" name="transformation_start_scale_x_tag"/>
+28 −22
Original line number Diff line number Diff line
@@ -17,11 +17,15 @@ package com.android.systemui.plugins;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Activity;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -34,6 +38,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.HandlerThread;
import android.os.UserHandle;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;

@@ -72,7 +77,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
        mMockPm = mock(PackageManager.class);
        mMockListener = mock(PluginListener.class);
        mMockManager = mock(PluginManager.class);
        when(mMockManager.getClassLoader(Mockito.any(), Mockito.any()))
        when(mMockManager.getClassLoader(any(), any()))
                .thenReturn(getClass().getClassLoader());
        mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
                mMockListener, true, mHandlerThread.getLooper(), 1, mMockManager, true);
@@ -87,8 +92,8 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
    }

    @Test
    public void testNoPlugins() {
        when(mMockPm.queryIntentServices(Mockito.any(), Mockito.anyInt())).thenReturn(
    public void testNoPlugins() throws Exception {
        when(mMockPm.queryIntentServices(any(), anyInt())).thenReturn(
                Collections.emptyList());
        mPluginInstanceManager.loadAll();

@@ -100,7 +105,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
    }

    @Test
    public void testPluginCreate() {
    public void testPluginCreate() throws Exception {
        createPlugin();

        // Verify startup lifecycle
@@ -110,7 +115,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
    }

    @Test
    public void testPluginDestroy() {
    public void testPluginDestroy() throws Exception {
        createPlugin(); // Get into valid created state.

        mPluginInstanceManager.destroy();
@@ -124,7 +129,9 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
    }

    @Test
    public void testIncorrectVersion() {
    public void testIncorrectVersion() throws Exception {
        NotificationManager nm = mock(NotificationManager.class);
        mContext.addMockSystemService(Context.NOTIFICATION_SERVICE, nm);
        setupFakePmQuery();
        when(sMockPlugin.getVersion()).thenReturn(2);

@@ -136,10 +143,12 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
        // Plugin shouldn't be connected because it is the wrong version.
        verify(mMockListener, Mockito.never()).onPluginConnected(
                ArgumentCaptor.forClass(Plugin.class).capture());
        verify(nm).notifyAsUser(eq(TestPlugin.class.getName()), eq(R.id.notification_plugin), any(),
                eq(UserHandle.ALL));
    }

    @Test
    public void testReloadOnChange() {
    public void testReloadOnChange() throws Exception {
        createPlugin(); // Get into valid created state.

        mPluginInstanceManager.onPackageChange("com.android.systemui");
@@ -159,7 +168,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
    }

    @Test
    public void testNonDebuggable() {
    public void testNonDebuggable() throws Exception {
        // Create a version that thinks the build is not debuggable.
        mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
                mMockListener, true, mHandlerThread.getLooper(), 1, mMockManager, false);
@@ -176,7 +185,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
    }

    @Test
    public void testCheckAndDisable() {
    public void testCheckAndDisable() throws Exception {
        createPlugin(); // Get into valid created state.

        // Start with an unrelated class.
@@ -197,7 +206,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
    }

    @Test
    public void testDisableAll() {
    public void testDisableAll() throws Exception {
        createPlugin(); // Get into valid created state.

        mPluginInstanceManager.disableAll();
@@ -208,29 +217,26 @@ public class PluginInstanceManagerTest extends SysuiTestCase {
                ArgumentCaptor.forClass(int.class).capture());
    }

    private void setupFakePmQuery() {
    private void setupFakePmQuery() throws Exception {
        List<ResolveInfo> list = new ArrayList<>();
        ResolveInfo info = new ResolveInfo();
        info.serviceInfo = new ServiceInfo();
        info.serviceInfo = mock(ServiceInfo.class);
        info.serviceInfo.packageName = "com.android.systemui";
        info.serviceInfo.name = TestPlugin.class.getName();
        when(info.serviceInfo.loadLabel(any())).thenReturn("Test Plugin");
        list.add(info);
        when(mMockPm.queryIntentServices(Mockito.any(), Mockito.anyInt())).thenReturn(list);
        when(mMockPm.queryIntentServices(any(), Mockito.anyInt())).thenReturn(list);
        when(mMockPm.getServiceInfo(any(), anyInt())).thenReturn(info.serviceInfo);

        when(mMockPm.checkPermission(Mockito.anyString(), Mockito.anyString())).thenReturn(
                PackageManager.PERMISSION_GRANTED);

        try {
        ApplicationInfo appInfo = getContext().getApplicationInfo();
        when(mMockPm.getApplicationInfo(Mockito.anyString(), Mockito.anyInt())).thenReturn(
                appInfo);
        } catch (NameNotFoundException e) {
            // Shouldn't be possible, but if it is, we want to fail.
            throw new RuntimeException(e);
        }
    }

    private void createPlugin() {
    private void createPlugin() throws Exception {
        setupFakePmQuery();

        mPluginInstanceManager.loadAll();
+25 −0
Original line number Diff line number Diff line
@@ -13,10 +13,17 @@
 */
package com.android.systemui.plugins;

import static org.mockito.Matchers.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.PackageManager;
import android.net.Uri;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;

@@ -113,6 +120,24 @@ public class PluginManagerTest extends SysuiTestCase {
                ArgumentCaptor.forClass(Throwable.class).capture());
    }

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

        ComponentName testComponent = new ComponentName(getContext().getPackageName(),
                PluginManagerTest.class.getName());
        Intent intent = new Intent(PluginManager.DISABLE_PLUGIN);
        intent.setData(Uri.parse("package://" + testComponent.flattenToString()));
        mPluginManager.onReceive(mContext, intent);
        verify(nm).cancel(eq(testComponent.getClassName()), eq(R.id.notification_plugin));
        verify(pm).setComponentEnabledSetting(eq(testComponent),
                eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
                eq(PackageManager.DONT_KILL_APP));
    }

    private void resetExceptionHandler() {
        mPluginExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        // Set back the real exception handler so the test can crash if it wants to.
Loading