Add notifications for incorrect plugin versions
Test: runtest systemui Change-Id: Ic59a583202a8a20fbfc6fb504e6ab60ecc71ce78
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user