Add notifications for incorrect plugin versions

Test: runtest systemui
Change-Id: Ic59a583202a8a20fbfc6fb504e6ab60ecc71ce78
This commit is contained in:
Jason Monk
2017-01-04 14:17:47 -05:00
parent 467085866f
commit 26bc8996c8
6 changed files with 134 additions and 25 deletions

View File

@@ -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);

View File

@@ -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();

View File

@@ -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"/>

View File

@@ -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);
}
ApplicationInfo appInfo = getContext().getApplicationInfo();
when(mMockPm.getApplicationInfo(Mockito.anyString(), Mockito.anyInt())).thenReturn(
appInfo);
}
private void createPlugin() {
private void createPlugin() throws Exception {
setupFakePmQuery();
mPluginInstanceManager.loadAll();

View File

@@ -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.

View File

@@ -23,6 +23,7 @@ import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.Handler;
import android.os.IBinder;
@@ -42,6 +43,7 @@ public class TestableContext extends ContextWrapper {
private ArrayMap<ComponentName, IBinder> mMockServices;
private ArrayMap<ServiceConnection, ComponentName> mActiveServices;
private PackageManager mMockPackageManager;
private Tracker mReceiver;
private Tracker mService;
private Tracker mComponent;
@@ -59,6 +61,18 @@ public class TestableContext extends ContextWrapper {
mComponent = test.getTracker("component");
}
public void setMockPackageManager(PackageManager mock) {
mMockPackageManager = mock;
}
@Override
public PackageManager getPackageManager() {
if (mMockPackageManager != null) {
return mMockPackageManager;
}
return super.getPackageManager();
}
@Override
public Resources getResources() {
return super.getResources();