diff options
32 files changed, 562 insertions, 109 deletions
diff --git a/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java index 13fc76c5da49..79a0c3599078 100644 --- a/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java +++ b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java @@ -24,7 +24,9 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver.InternalInsetsInfo; import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; import com.android.systemui.plugins.OverlayPlugin; +import com.android.systemui.plugins.annotations.Requires; +@Requires(target = OverlayPlugin.class, version = OverlayPlugin.VERSION) public class SampleOverlayPlugin implements OverlayPlugin { private static final String TAG = "SampleOverlayPlugin"; private Context mPluginContext; @@ -36,12 +38,6 @@ public class SampleOverlayPlugin implements OverlayPlugin { private float mStatusBarHeight; @Override - public int getVersion() { - Log.d(TAG, "getVersion " + VERSION); - return VERSION; - } - - @Override public void onCreate(Context sysuiContext, Context pluginContext) { Log.d(TAG, "onCreate"); mPluginContext = pluginContext; diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java index 9c173bd16967..97dbafd65d33 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java @@ -14,6 +14,8 @@ package com.android.systemui.plugins; +import com.android.systemui.plugins.annotations.ProvidesInterface; + import android.content.Intent; import android.graphics.drawable.Drawable; @@ -21,6 +23,7 @@ import android.graphics.drawable.Drawable; * An Intent Button represents a triggerable element in SysUI that consists of an * Icon and an intent to trigger when it is activated (clicked, swiped, etc.). */ +@ProvidesInterface(version = IntentButtonProvider.VERSION) public interface IntentButtonProvider extends Plugin { public static final int VERSION = 1; diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java index f5074f75b7a4..61aa60bb9675 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java @@ -13,12 +13,15 @@ */ package com.android.systemui.plugins; +import com.android.systemui.plugins.annotations.ProvidesInterface; + import android.view.View; +@ProvidesInterface(action = OverlayPlugin.ACTION, version = OverlayPlugin.VERSION) public interface OverlayPlugin extends Plugin { String ACTION = "com.android.systemui.action.PLUGIN_OVERLAY"; - int VERSION = 1; + int VERSION = 2; void setup(View statusBar, View navBar); diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java index e75ecb7a127b..bb93367c3791 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java @@ -13,6 +13,8 @@ */ package com.android.systemui.plugins; +import com.android.systemui.plugins.annotations.Requires; + import android.content.Context; /** @@ -111,18 +113,13 @@ import android.content.Context; public interface Plugin { /** - * Should be implemented as the following directly referencing the version constant - * from the plugin interface being implemented, this will allow recompiles to automatically - * pick up the current version. - * <pre class="prettyprint"> - * {@literal - * public int getVersion() { - * return VERSION; - * } - * } - * @return + * @deprecated + * @see Requires */ - int getVersion(); + default int getVersion() { + // Default of -1 indicates the plugin supports the new Requires model. + return -1; + } default void onCreate(Context sysuiContext, Context pluginContext) { } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Dependencies.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Dependencies.java new file mode 100644 index 000000000000..dbbf047519bb --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Dependencies.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 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.plugins.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Used for repeated @DependsOn internally, not for plugin + * use. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Dependencies { + DependsOn[] value(); +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/DependsOn.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/DependsOn.java new file mode 100644 index 000000000000..b81d67306307 --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/DependsOn.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 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.plugins.annotations; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Used to indicate that an interface in the plugin library needs another + * interface to function properly. When this is added, it will be enforced + * that all plugins that @Requires the annotated interface also @Requires + * the specified class as well. + */ +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(value = Dependencies.class) +public @interface DependsOn { + Class<?> target(); + +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/ProvidesInterface.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/ProvidesInterface.java new file mode 100644 index 000000000000..d0e14b8657ff --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/ProvidesInterface.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 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.plugins.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Should be added to all interfaces in plugin lib to specify their + * current version and optionally their action to implement the plugin. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface ProvidesInterface { + int version(); + + String action() default ""; + +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requirements.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requirements.java new file mode 100644 index 000000000000..9cfa279b9c19 --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requirements.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 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.plugins.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Used for repeated @Requires internally, not for plugin + * use. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Requirements { + Requires[] value(); +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requires.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requires.java new file mode 100644 index 000000000000..e1b1303b8cb5 --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/annotations/Requires.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 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.plugins.annotations; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Used to annotate which interfaces a given plugin depends on. + * + * At minimum all plugins should have at least one @Requires annotation + * for the plugin interface that they are implementing. They will also + * need an @Requires for each class that the plugin interface @DependsOn. + */ +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(value = Requirements.class) +public @interface Requires { + Class<?> target(); + int version(); +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/doze/DozeProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/doze/DozeProvider.java index 688df466d244..068848120103 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/doze/DozeProvider.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/doze/DozeProvider.java @@ -20,10 +20,12 @@ import android.app.PendingIntent; import android.content.Context; import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.annotations.ProvidesInterface; /** * Provides a {@link DozeUi}. */ +@ProvidesInterface(action = DozeProvider.ACTION, version = DozeProvider.VERSION) public interface DozeProvider extends Plugin { String ACTION = "com.android.systemui.action.PLUGIN_DOZE"; diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java index e21a282581a4..b7467eba1b9c 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java @@ -14,29 +14,32 @@ package com.android.systemui.plugins.qs; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.PendingIntent; +import com.android.systemui.plugins.FragmentBase; +import com.android.systemui.plugins.annotations.DependsOn; +import com.android.systemui.plugins.annotations.ProvidesInterface; +import com.android.systemui.plugins.qs.QS.Callback; +import com.android.systemui.plugins.qs.QS.DetailAdapter; +import com.android.systemui.plugins.qs.QS.HeightListener; + import android.content.Context; import android.content.Intent; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; -import android.widget.FrameLayout; import android.widget.RelativeLayout; -import com.android.systemui.plugins.FragmentBase; - /** * Fragment that contains QS in the notification shade. Most of the interface is for * handling the expand/collapsing of the view interaction. */ +@ProvidesInterface(action = QS.ACTION, version = QS.VERSION) +@DependsOn(target = HeightListener.class) +@DependsOn(target = Callback.class) +@DependsOn(target = DetailAdapter.class) public interface QS extends FragmentBase { public static final String ACTION = "com.android.systemui.action.PLUGIN_QS"; - // This should be incremented any time this class or ActivityStarter or BaseStatusBarHeader - // change in incompatible ways. public static final int VERSION = 5; String TAG = "QS"; @@ -64,17 +67,23 @@ public interface QS extends FragmentBase { public abstract void setContainer(ViewGroup container); + @ProvidesInterface(version = HeightListener.VERSION) public interface HeightListener { + public static final int VERSION = 1; void onQsHeightChanged(); } + @ProvidesInterface(version = Callback.VERSION) public interface Callback { + public static final int VERSION = 1; void onShowingDetail(DetailAdapter detail, int x, int y); void onToggleStateChanged(boolean state); void onScanStateChanged(boolean state); } + @ProvidesInterface(version = DetailAdapter.VERSION) public interface DetailAdapter { + public static final int VERSION = 1; CharSequence getTitle(); Boolean getToggleState(); default boolean getToggleEnabled() { @@ -92,7 +101,9 @@ public interface QS extends FragmentBase { default boolean hasHeader() { return true; } } + @ProvidesInterface(version = BaseStatusBarHeader.VERSION) public abstract static class BaseStatusBarHeader extends RelativeLayout { + public static final int VERSION = 1; public BaseStatusBarHeader(Context context, AttributeSet attrs) { super(context, attrs); diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java index 41a0907c3228..bc98c8ec388f 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java @@ -10,7 +10,10 @@ import android.view.View; import java.util.ArrayList; import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.annotations.ProvidesInterface; +@ProvidesInterface(action = NotificationMenuRowProvider.ACTION, + version = NotificationMenuRowProvider.VERSION) public interface NotificationMenuRowProvider extends Plugin { public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_MENU_ROW"; diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java index d54e33fc4dff..5243228121c6 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java @@ -14,14 +14,15 @@ package com.android.systemui.plugins.statusbar.phone; -import android.annotation.DrawableRes; import android.annotation.Nullable; import android.graphics.drawable.Drawable; import android.view.View; import android.view.ViewGroup; import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.annotations.ProvidesInterface; +@ProvidesInterface(action = NavBarButtonProvider.ACTION, version = NavBarButtonProvider.VERSION) public interface NavBarButtonProvider extends Plugin { public static final String ACTION = "com.android.systemui.action.PLUGIN_NAV_BUTTON"; diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java index 918d6e96784d..ddee89ed74ec 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java @@ -17,7 +17,9 @@ package com.android.systemui.plugins.statusbar.phone; import android.view.MotionEvent; import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.annotations.ProvidesInterface; +@ProvidesInterface(action = NavGesture.ACTION, version = NavBarButtonProvider.VERSION) public interface NavGesture extends Plugin { public static final String ACTION = "com.android.systemui.action.PLUGIN_NAV_GESTURE"; diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java index 9cc66138cfb4..ddd48330e679 100644 --- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java +++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java @@ -53,8 +53,7 @@ public class PluginInflateContainer extends AutoReinflateContainer private static final String TAG = "PluginInflateContainer"; - private String mAction; - private int mVersion; + private Class<?> mClass; private View mPluginView; public PluginInflateContainer(Context context, @Nullable AttributeSet attrs) { @@ -62,28 +61,25 @@ public class PluginInflateContainer extends AutoReinflateContainer TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PluginInflateContainer); String viewType = a.getString(R.styleable.PluginInflateContainer_viewType); try { - Class c = Class.forName(viewType); - mAction = (String) c.getDeclaredField("ACTION").get(null); - mVersion = (int) c.getDeclaredField("VERSION").get(null); + mClass = Class.forName(viewType); } catch (Exception e) { Log.d(TAG, "Problem getting class info " + viewType, e); - mAction = null; - mVersion = 0; + mClass = null; } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - if (mAction != null) { - Dependency.get(PluginManager.class).addPluginListener(mAction, this, mVersion); + if (mClass != null) { + Dependency.get(PluginManager.class).addPluginListener(this, mClass); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - if (mAction != null) { + if (mClass != null) { Dependency.get(PluginManager.class).removePluginListener(this); } } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 6caee5f2220e..be6986750804 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -36,6 +36,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.media.RingtonePlayer; import com.android.systemui.pip.PipUI; import com.android.systemui.plugins.OverlayPlugin; +import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.PluginManager; import com.android.systemui.power.PowerUI; @@ -206,7 +207,7 @@ public class SystemUIApplication extends Application implements SysUiServiceProv mServices[i].onBootCompleted(); } } - Dependency.get(PluginManager.class).addPluginListener(OverlayPlugin.ACTION, + Dependency.get(PluginManager.class).addPluginListener( new PluginListener<OverlayPlugin>() { private ArraySet<OverlayPlugin> mOverlays; @@ -235,7 +236,7 @@ public class SystemUIApplication extends Application implements SysUiServiceProv Dependency.get(StatusBarWindowManager.class).setForcePluginOpen( mOverlays.size() != 0); } - }, OverlayPlugin.VERSION, true /* Allow multiple plugins */); + }, OverlayPlugin.class, true /* Allow multiple plugins */); mServicesStarted = true; } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java index 94dc9a344a34..6186df14ef3f 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java @@ -49,7 +49,7 @@ public class DozeService extends DreamService implements DozeMachine.Service { } DozeProvider provider = Dependency.get(PluginManager.class) - .getOneShotPlugin(DozeProvider.ACTION, DozeProvider.VERSION); + .getOneShotPlugin(DozeProvider.class); mDozeMachine = new DozeFactory(provider).assembleMachine(this); } diff --git a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java index 1eaca6ffd9db..03bb73da3902 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java @@ -44,8 +44,9 @@ public class PluginFragmentListener implements PluginListener<Plugin> { mDefaultClass = defaultFragment; } - public void startListening(String action, int version) { - mPluginManager.addPluginListener(action, this, version, false /* Only allow one */); + public void startListening() { + mPluginManager.addPluginListener(this, mExpectedInterface, + false /* Only allow one */); } public void stopListening() { diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java index dd1614b50ca6..e895fa226cf3 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java @@ -18,12 +18,10 @@ 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; @@ -40,6 +38,7 @@ import android.view.LayoutInflater; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.systemui.plugins.VersionInfo.InvalidVersionException; import java.util.ArrayList; import java.util.List; @@ -55,7 +54,7 @@ public class PluginInstanceManager<T extends Plugin> { private final PluginListener<T> mListener; private final String mAction; private final boolean mAllowMultiple; - private final int mVersion; + private final VersionInfo mVersion; @VisibleForTesting final MainHandler mMainHandler; @@ -66,14 +65,14 @@ public class PluginInstanceManager<T extends Plugin> { private final PluginManager mManager; PluginInstanceManager(Context context, String action, PluginListener<T> listener, - boolean allowMultiple, Looper looper, int version, PluginManager manager) { + boolean allowMultiple, Looper looper, VersionInfo version, PluginManager manager) { this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version, manager, Build.IS_DEBUGGABLE); } @VisibleForTesting PluginInstanceManager(Context context, PackageManager pm, String action, - PluginListener<T> listener, boolean allowMultiple, Looper looper, int version, + PluginListener<T> listener, boolean allowMultiple, Looper looper, VersionInfo version, PluginManager manager, boolean debuggable) { mMainHandler = new MainHandler(Looper.getMainLooper()); mPluginHandler = new PluginHandler(looper); @@ -301,8 +300,14 @@ public class PluginInstanceManager<T extends Plugin> { Context pluginContext = new PluginContextWrapper( mContext.createApplicationContext(info, 0), classLoader); Class<?> pluginClass = Class.forName(cls, true, classLoader); + // TODO: Only create the plugin before version check if we need it for + // legacy version check. T plugin = (T) pluginClass.newInstance(); - if (plugin.getVersion() != mVersion) { + try { + checkVersion(pluginClass, plugin, mVersion); + if (DEBUG) Log.d(TAG, "createPlugin"); + return new PluginInfo(pkg, cls, plugin, pluginContext); + } catch (InvalidVersionException e) { final int icon = mContext.getResources().getIdentifier("tuner", "drawable", mContext.getPackageName()); final int color = Resources.getSystem().getIdentifier( @@ -318,20 +323,18 @@ public class PluginInstanceManager<T extends Plugin> { String label = cls; try { label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString(); - } catch (NameNotFoundException e) { + } catch (NameNotFoundException e2) { } - if (plugin.getVersion() < mVersion) { + if (!e.isTooNew()) { // 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); + + " version.\n" + e.getMessage()); } 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); + + e.getMessage()); } Intent i = new Intent(PluginManager.DISABLE_PLUGIN).setData( Uri.parse("package://" + component.flattenToString())); @@ -345,13 +348,24 @@ public class PluginInstanceManager<T extends Plugin> { + ", expected " + mVersion); return null; } - if (DEBUG) Log.d(TAG, "createPlugin"); - return new PluginInfo(pkg, cls, plugin, pluginContext); } catch (Exception e) { Log.w(TAG, "Couldn't load plugin: " + pkg, e); return null; } } + + private void checkVersion(Class<?> pluginClass, T plugin, VersionInfo version) + throws InvalidVersionException { + VersionInfo pv = new VersionInfo().addClass(pluginClass); + if (pv.hasVersionInfo()) { + version.checkVersion(pv); + } else { + int fallbackVersion = plugin.getVersion(); + if (fallbackVersion != version.getDefaultVersion()) { + throw new InvalidVersionException("Invalid legacy version", false); + } + } + } } public static class PluginContextWrapper extends ContextWrapper { diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java index cef485e673ad..8b4bd7bc9f7b 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java @@ -33,6 +33,7 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.SystemProperties; import android.os.UserHandle; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -40,6 +41,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper; import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; +import com.android.systemui.plugins.annotations.ProvidesInterface; import dalvik.system.PathClassLoader; @@ -93,7 +95,18 @@ public class PluginManager extends BroadcastReceiver { Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler); } - public <T extends Plugin> T getOneShotPlugin(String action, int version) { + public <T extends Plugin> T getOneShotPlugin(Class<T> cls) { + ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class); + if (info == null) { + throw new RuntimeException(cls + " doesn't provide an interface"); + } + if (TextUtils.isEmpty(info.action())) { + throw new RuntimeException(cls + " doesn't provide an action"); + } + return getOneShotPlugin(info.action(), cls); + } + + public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) { if (!isDebuggable) { // Never ever ever allow these on production builds, they are only for prototyping. return null; @@ -102,7 +115,7 @@ public class PluginManager extends BroadcastReceiver { throw new RuntimeException("Must be called from UI thread"); } PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, null, - false, mBackgroundThread.getLooper(), version, this); + false, mBackgroundThread.getLooper(), cls, this); mPluginPrefs.addAction(action); PluginInfo<T> info = p.getPlugin(); if (info != null) { @@ -114,20 +127,36 @@ public class PluginManager extends BroadcastReceiver { return null; } + public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) { + addPluginListener(listener, cls, false); + } + + public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls, + boolean allowMultiple) { + ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class); + if (info == null) { + throw new RuntimeException(cls + " doesn't provide an interface"); + } + if (TextUtils.isEmpty(info.action())) { + throw new RuntimeException(cls + " doesn't provide an action"); + } + addPluginListener(info.action(), listener, cls, allowMultiple); + } + public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, - int version) { - addPluginListener(action, listener, version, false); + Class<?> cls) { + addPluginListener(action, listener, cls, false); } public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, - int version, boolean allowMultiple) { + Class cls, boolean allowMultiple) { if (!isDebuggable) { // Never ever ever allow these on production builds, they are only for prototyping. return; } mPluginPrefs.addAction(action); PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener, - allowMultiple, mBackgroundThread.getLooper(), version, this); + allowMultiple, mBackgroundThread.getLooper(), cls, this); p.loadAll(); mPluginMap.put(listener, p); startListening(); @@ -282,9 +311,9 @@ public class PluginManager extends BroadcastReceiver { public static class PluginInstanceManagerFactory { public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context, String action, PluginListener<T> listener, boolean allowMultiple, Looper looper, - int version, PluginManager manager) { + Class<?> cls, PluginManager manager) { return new PluginInstanceManager(context, action, listener, allowMultiple, looper, - version, manager); + new VersionInfo().addClass(cls), manager); } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginPrefs.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginPrefs.java index 3671b3c1689f..3671b3c1689f 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginPrefs.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginPrefs.java diff --git a/packages/SystemUI/src/com/android/systemui/plugins/VersionInfo.java b/packages/SystemUI/src/com/android/systemui/plugins/VersionInfo.java new file mode 100644 index 000000000000..84f7761a8043 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/plugins/VersionInfo.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2017 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.plugins; + +import com.android.systemui.plugins.annotations.Dependencies; +import com.android.systemui.plugins.annotations.DependsOn; +import com.android.systemui.plugins.annotations.ProvidesInterface; +import com.android.systemui.plugins.annotations.Requirements; +import com.android.systemui.plugins.annotations.Requires; + +import android.util.ArrayMap; + +public class VersionInfo { + + private final ArrayMap<Class<?>, Version> mVersions = new ArrayMap<>(); + private Class<?> mDefault; + + public boolean hasVersionInfo() { + return !mVersions.isEmpty(); + } + + public int getDefaultVersion() { + return mVersions.get(mDefault).mVersion; + } + + public VersionInfo addClass(Class<?> cls) { + if (mDefault == null) { + // The legacy default version is from the first class we add. + mDefault = cls; + } + addClass(cls, false); + return this; + } + + private void addClass(Class<?> cls, boolean required) { + ProvidesInterface provider = cls.getDeclaredAnnotation(ProvidesInterface.class); + if (provider != null) { + mVersions.put(cls, new Version(provider.version(), true)); + } + Requires requires = cls.getDeclaredAnnotation(Requires.class); + if (requires != null) { + mVersions.put(requires.target(), new Version(requires.version(), required)); + } + Requirements requirements = cls.getDeclaredAnnotation(Requirements.class); + if (requirements != null) { + for (Requires r : requirements.value()) { + mVersions.put(r.target(), new Version(r.version(), required)); + } + } + DependsOn depends = cls.getDeclaredAnnotation(DependsOn.class); + if (depends != null) { + addClass(depends.target(), true); + } + Dependencies dependencies = cls.getDeclaredAnnotation(Dependencies.class); + if (dependencies != null) { + for (DependsOn d : dependencies.value()) { + addClass(d.target(), true); + } + } + } + + public void checkVersion(VersionInfo plugin) throws InvalidVersionException { + ArrayMap<Class<?>, Version> versions = new ArrayMap<>(mVersions); + plugin.mVersions.forEach((aClass, version) -> { + Version v = versions.remove(aClass); + if (v == null) { + v = createVersion(aClass); + } + if (v == null) { + throw new InvalidVersionException(aClass.getSimpleName() + + " does not provide an interface", false); + } + if (v.mVersion != version.mVersion) { + throw new InvalidVersionException(aClass, v.mVersion < version.mVersion, v.mVersion, + version.mVersion); + } + }); + versions.forEach((aClass, version) -> { + if (version.mRequired) { + throw new InvalidVersionException("Missing required dependency " + + aClass.getSimpleName(), false); + } + }); + } + + private Version createVersion(Class<?> cls) { + ProvidesInterface provider = cls.getDeclaredAnnotation(ProvidesInterface.class); + if (provider != null) { + return new Version(provider.version(), false); + } + return null; + } + + public static class InvalidVersionException extends RuntimeException { + private final boolean mTooNew; + + public InvalidVersionException(String str, boolean tooNew) { + super(str); + mTooNew = tooNew; + } + + public InvalidVersionException(Class<?> cls, boolean tooNew, int expected, int actual) { + super(cls.getSimpleName() + " expected version " + expected + " but had " + actual); + mTooNew = tooNew; + } + + public boolean isTooNew() { + return mTooNew; + } + } + + private static class Version { + + private final int mVersion; + private final boolean mRequired; + + public Version(int version, boolean required) { + mVersion = version; + mRequired = required; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 504678c7f1c5..1569b0ca0249 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -86,6 +86,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback { setOrientation(VERTICAL); + mBrightnessView = LayoutInflater.from(context).inflate( + R.layout.quick_settings_brightness_dialog, this, false); + addView(mBrightnessView); + setupTileLayout(); mFooter = new QSFooter(this, context); @@ -100,10 +104,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback { updateResources(); - mBrightnessView = LayoutInflater.from(context).inflate( - R.layout.quick_settings_brightness_dialog, this, false); - addView(mBrightnessView); - mBrightnessController = new BrightnessController(getContext(), (ImageView) findViewById(R.id.brightness_icon), (ToggleSliderView) findViewById(R.id.brightness_slider)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java index 355022f9794c..534a71936b5a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java @@ -90,8 +90,7 @@ public class NotificationMenuRow extends FrameLayout protected void onAttachedToWindow() { super.onAttachedToWindow(); Dependency.get(PluginManager.class).addPluginListener( - NotificationMenuRowProvider.ACTION, this, - NotificationMenuRowProvider.VERSION, false /* Allow multiple */); + this, NotificationMenuRowProvider.class, false /* Allow multiple */); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 2836f41b04f5..2b335f44b485 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -261,9 +261,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL super.onAttachedToWindow(); mAccessibilityController.addStateChangedCallback(this); Dependency.get(PluginManager.class).addPluginListener(RIGHT_BUTTON_PLUGIN, - mRightListener, IntentButtonProvider.VERSION, false /* Only allow one */); + mRightListener, IntentButtonProvider.class, false /* Only allow one */); Dependency.get(PluginManager.class).addPluginListener(LEFT_BUTTON_PLUGIN, - mLeftListener, IntentButtonProvider.VERSION, false /* Only allow one */); + mLeftListener, IntentButtonProvider.class, false /* Only allow one */); Dependency.get(TunerService.class).addTunable(this, LockscreenFragment.LOCKSCREEN_LEFT_BUTTON, LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java index 5fb99dabfeab..720ca1499bf1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java @@ -138,8 +138,8 @@ public class NavigationBarInflaterView extends FrameLayout super.onAttachedToWindow(); Dependency.get(TunerService.class).addTunable(this, NAV_BAR_VIEWS, NAV_BAR_LEFT, NAV_BAR_RIGHT); - Dependency.get(PluginManager.class).addPluginListener(NavBarButtonProvider.ACTION, this, - NavBarButtonProvider.VERSION, true /* Allow multiple */); + Dependency.get(PluginManager.class).addPluginListener(this, + NavBarButtonProvider.class, true /* Allow multiple */); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 5d13289ef9cc..ad875f13d27e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -783,8 +783,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav protected void onAttachedToWindow() { super.onAttachedToWindow(); onPluginDisconnected(null); // Create default gesture helper - Dependency.get(PluginManager.class).addPluginListener(NavGesture.ACTION, this, - NavGesture.VERSION, false /* Only one */); + Dependency.get(PluginManager.class).addPluginListener(this, + NavGesture.class, false /* Only one */); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index ac13cf4eb111..55d66ee03e08 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1129,7 +1129,7 @@ public class StatusBar extends SystemUI implements DemoMode, .replace(R.id.qs_frame, new QSFragment(), QS.TAG) .commit(); new PluginFragmentListener(container, QS.TAG, QSFragment.class, QS.class) - .startListening(QS.ACTION, QS.VERSION); + .startListening(); final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this, mIconController); mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow); diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java index 3715df2a42c0..658966cdde17 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java @@ -17,14 +17,27 @@ package com.android.systemui.plugins; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; 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.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; +import com.android.systemui.plugins.VersionInfo.InvalidVersionException; +import com.android.systemui.plugins.annotations.Requires; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + import android.app.Activity; import android.app.NotificationManager; import android.content.BroadcastReceiver; @@ -35,7 +48,6 @@ 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.pm.ServiceInfo; import android.os.HandlerThread; @@ -44,17 +56,6 @@ import android.support.test.annotation.UiThreadTest; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -72,6 +73,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { private PluginListener mMockListener; private PluginInstanceManager mPluginInstanceManager; private PluginManager mMockManager; + private VersionInfo mMockVersionInfo; @Before public void setup() throws Exception { @@ -83,8 +85,10 @@ public class PluginInstanceManagerTest extends SysuiTestCase { mMockManager = mock(PluginManager.class); when(mMockManager.getClassLoader(any(), any())) .thenReturn(getClass().getClassLoader()); + mMockVersionInfo = mock(VersionInfo.class); mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction", - mMockListener, true, mHandlerThread.getLooper(), 1, mMockManager, true); + mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo, + mMockManager, true); sMockPlugin = mock(Plugin.class); when(sMockPlugin.getVersion()).thenReturn(1); } @@ -145,7 +149,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { NotificationManager nm = mock(NotificationManager.class); mContext.addMockSystemService(Context.NOTIFICATION_SERVICE, nm); setupFakePmQuery(); - when(sMockPlugin.getVersion()).thenReturn(2); + doThrow(new InvalidVersionException("", false)).when(mMockVersionInfo).checkVersion(any()); mPluginInstanceManager.loadAll(); @@ -181,7 +185,8 @@ public class PluginInstanceManagerTest extends SysuiTestCase { 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); + mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo, + mMockManager, false); setupFakePmQuery(); mPluginInstanceManager.loadAll(); @@ -270,6 +275,9 @@ public class PluginInstanceManagerTest extends SysuiTestCase { } } + // This target class doesn't matter, it just needs to have a Requires to hit the flow where + // the mock version info is called. + @Requires(target = PluginManagerTest.class, version = 1) public static class TestPlugin implements Plugin { @Override public int getVersion() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java index a58407b2ae23..09ac5a633cb0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java @@ -32,6 +32,7 @@ import android.test.suitebuilder.annotation.SmallTest; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.annotations.ProvidesInterface; import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; import com.android.systemui.plugins.PluginManager.PluginInstanceManagerFactory; @@ -63,7 +64,7 @@ public class PluginManagerTest extends SysuiTestCase { mMockFactory = mock(PluginInstanceManagerFactory.class); mMockPluginInstance = mock(PluginInstanceManager.class); when(mMockFactory.createPluginInstanceManager(Mockito.any(), Mockito.any(), Mockito.any(), - Mockito.anyBoolean(), Mockito.any(), Mockito.anyInt(), Mockito.any())) + Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.any())) .thenReturn(mMockPluginInstance); mPluginManager = new PluginManager(getContext(), mMockFactory, true, mMockExceptionHandler); resetExceptionHandler(); @@ -76,20 +77,20 @@ public class PluginManagerTest extends SysuiTestCase { Plugin mockPlugin = mock(Plugin.class); when(mMockPluginInstance.getPlugin()).thenReturn(new PluginInfo(null, null, mockPlugin, null)); - Plugin result = mPluginManager.getOneShotPlugin("myAction", 1); + Plugin result = mPluginManager.getOneShotPlugin("myAction", TestPlugin.class); assertTrue(result == mockPlugin); } @Test public void testAddListener() { - mPluginManager.addPluginListener("myAction", mMockListener, 1); + mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); verify(mMockPluginInstance).loadAll(); } @Test public void testRemoveListener() { - mPluginManager.addPluginListener("myAction", mMockListener, 1); + mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); mPluginManager.removePluginListener(mMockListener); verify(mMockPluginInstance).destroy(); @@ -101,16 +102,16 @@ public class PluginManagerTest extends SysuiTestCase { mMockExceptionHandler); resetExceptionHandler(); - mPluginManager.addPluginListener("myAction", mMockListener, 1); + mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); verify(mMockPluginInstance, Mockito.never()).loadAll(); - assertNull(mPluginManager.getOneShotPlugin("myPlugin", 1)); + assertNull(mPluginManager.getOneShotPlugin("myPlugin", TestPlugin.class)); verify(mMockPluginInstance, Mockito.never()).getPlugin(); } @Test public void testExceptionHandler_foundPlugin() { - mPluginManager.addPluginListener("myAction", mMockListener, 1); + mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); when(mMockPluginInstance.checkAndDisable(Mockito.any())).thenReturn(true); mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable()); @@ -125,7 +126,7 @@ public class PluginManagerTest extends SysuiTestCase { @Test public void testExceptionHandler_noFoundPlugin() { - mPluginManager.addPluginListener("myAction", mMockListener, 1); + mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); when(mMockPluginInstance.checkAndDisable(Mockito.any())).thenReturn(false); mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable()); @@ -161,4 +162,10 @@ public class PluginManagerTest extends SysuiTestCase { // Set back the real exception handler so the test can crash if it wants to. Thread.setDefaultUncaughtExceptionHandler(mRealExceptionHandler); } + + @ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION) + public static interface TestPlugin extends Plugin { + public static final String ACTION = "testAction"; + public static final int VERSION = 1; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/VersionInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/VersionInfoTest.java new file mode 100644 index 000000000000..0d87d6b06c4e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/VersionInfoTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017 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.plugins; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.VersionInfo.InvalidVersionException; +import com.android.systemui.plugins.annotations.Requires; +import com.android.systemui.plugins.qs.QS; +import com.android.systemui.plugins.qs.QS.Callback; +import com.android.systemui.plugins.qs.QS.DetailAdapter; +import com.android.systemui.plugins.qs.QS.HeightListener; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class VersionInfoTest extends SysuiTestCase { + + @Rule + public ExpectedException mThrown = ExpectedException.none(); + + @Test + public void testHasInfo() { + VersionInfo info = new VersionInfo(); + info.addClass(VersionInfoTest.class); // Has no annotations. + assertFalse(info.hasVersionInfo()); + + info.addClass(OverlayPlugin.class); + assertTrue(info.hasVersionInfo()); + } + + @Test + public void testSingleProvides() { + VersionInfo overlay = new VersionInfo().addClass(OverlayPlugin.class); + VersionInfo impl = new VersionInfo().addClass(OverlayImpl.class); + overlay.checkVersion(impl); + } + + @Test + public void testIncorrectVersion() { + VersionInfo overlay = new VersionInfo().addClass(OverlayPlugin.class); + VersionInfo impl = new VersionInfo().addClass(OverlayImplIncorrectVersion.class); + mThrown.expect(InvalidVersionException.class); + overlay.checkVersion(impl); + } + + @Test + public void testMissingRequired() { + VersionInfo overlay = new VersionInfo().addClass(OverlayPlugin.class); + VersionInfo impl = new VersionInfo(); + mThrown.expect(InvalidVersionException.class); + overlay.checkVersion(impl); + } + + @Test + public void testMissingDependencies() { + VersionInfo overlay = new VersionInfo().addClass(QS.class); + VersionInfo impl = new VersionInfo().addClass(QSImplNoDeps.class); + mThrown.expect(InvalidVersionException.class); + overlay.checkVersion(impl); + } + + @Test + public void testHasDependencies() { + VersionInfo overlay = new VersionInfo().addClass(QS.class); + VersionInfo impl = new VersionInfo().addClass(QSImpl.class); + overlay.checkVersion(impl); + } + + @Requires(target = OverlayPlugin.class, version = OverlayPlugin.VERSION) + public static class OverlayImpl { + } + + @Requires(target = OverlayPlugin.class, version = 0) + public static class OverlayImplIncorrectVersion { + } + + @Requires(target = QS.class, version = QS.VERSION) + public static class QSImplNoDeps { + } + + @Requires(target = QS.class, version = QS.VERSION) + @Requires(target = Callback.class, version = Callback.VERSION) + @Requires(target = HeightListener.class, version = HeightListener.VERSION) + @Requires(target = DetailAdapter.class, version = DetailAdapter.VERSION) + public static class QSImpl { + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java index d1abccaa6e76..59a93618cdb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java @@ -31,13 +31,7 @@ public class FakePluginManager extends PluginManager { @Override public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, - int version) { - mLeakChecker.addCallback(listener); - } - - @Override - public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, - int version, boolean allowMultiple) { + Class cls, boolean allowMultiple) { mLeakChecker.addCallback(listener); } @@ -47,7 +41,7 @@ public class FakePluginManager extends PluginManager { } @Override - public <T extends Plugin> T getOneShotPlugin(String action, int version) { + public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) { return null; } } |