Merge "New system for versioning sysui plugins"
This commit is contained in:
@@ -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;
|
||||
@@ -35,12 +37,6 @@ public class SampleOverlayPlugin implements OverlayPlugin {
|
||||
private boolean mCollapseDesired;
|
||||
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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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 "";
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
@@ -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(String action, PluginListener<T> listener,
|
||||
int version) {
|
||||
addPluginListener(action, listener, version, false);
|
||||
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, boolean allowMultiple) {
|
||||
Class<?> cls) {
|
||||
addPluginListener(action, listener, cls, false);
|
||||
}
|
||||
|
||||
public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user