Add plugin to swap out recents

Adds new mechanism for activities in sysui to be replaced by plugins
and allows that to happen for recents.

Test: runtest systemui
Change-Id: I8b570ce7c57484c58a52afe5e247d24ebfa0c57f
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginActivity.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginActivity.java
new file mode 100644
index 0000000..925214e
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginActivity.java
@@ -0,0 +1,92 @@
+/*
+ * 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 android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A PluginActivity is an activity that replaces another full activity (e.g. RecentsActivity)
+ * at runtime within the sysui process.
+ */
+@ProvidesInterface(version = PluginActivity.VERSION)
+public abstract class PluginActivity extends Activity implements Plugin {
+
+    public static final int VERSION = 1;
+
+    public static final String ACTION_RECENTS = "com.android.systemui.action.PLUGIN_RECENTS";
+
+    private Context mSysuiContext;
+    private boolean mSettingActionBar;
+
+    @Override
+    public final void onCreate(Context sysuiContext, Context pluginContext) {
+        mSysuiContext = sysuiContext;
+        super.attachBaseContext(pluginContext);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Theme theme = getClass().getDeclaredAnnotation(Theme.class);
+        if (theme != null && theme.value() != 0) {
+            setTheme(theme.value());
+        }
+        mSettingActionBar = true;
+        getActionBar();
+        mSettingActionBar = false;
+    }
+
+    @Override
+    public Resources getResources() {
+        return mSettingActionBar ? mSysuiContext.getResources() : super.getResources();
+    }
+
+    @Override
+    protected void attachBaseContext(Context newBase) {
+        mSysuiContext = newBase;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+
+    public Context getSysuiContext() {
+        return mSysuiContext;
+    }
+
+    public Context getPluginContext() {
+        return getBaseContext();
+    }
+
+    /**
+     * Since PluginActivities are declared as services instead of activities (since they
+     * are plugins), they can't have a theme attached to them. Instead a PluginActivity
+     * can annotate itself with @Theme to specify the resource of the style it wants
+     * to be themed with.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface Theme {
+        int value();
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginDependency.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginDependency.java
index 25ce3dd..db2e376 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginDependency.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginDependency.java
@@ -21,6 +21,11 @@
     public static final int VERSION = 1;
     static DependencyProvider sProvider;
 
+    /**
+     * Allows a plugin to get a hold of static dependencies if they have declared dependence
+     * on their interface. For one-shot plugins this will only work during onCreate and will
+     * not work afterwards.
+     */
     public static <T> T get(Plugin p, Class<T> cls) {
         return sProvider.get(p, cls);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 1b694b3..bb44123 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -31,6 +31,7 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.PluginActivityManager;
 import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.PluginManagerImpl;
@@ -276,6 +277,9 @@
 
         mProviders.put(UiOffloadThread.class, UiOffloadThread::new);
 
+        mProviders.put(PluginActivityManager.class,
+                () -> new PluginActivityManager(mContext, getDependency(PluginManager.class)));
+
         // Put all dependencies above here so the factory can override them if it wants.
         SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 4a45997..fe6eb4b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui;
 
+import android.app.Activity;
 import android.app.ActivityThread;
 import android.app.Application;
 import android.content.BroadcastReceiver;
@@ -39,6 +40,7 @@
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.plugins.OverlayPlugin;
 import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.PluginActivityManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.power.PowerUI;
@@ -266,4 +268,10 @@
     public SystemUI[] getServices() {
         return mServices;
     }
+
+    @Override
+    public Activity instantiateActivity(ClassLoader cl, String className, Intent intent) {
+        if (!mServicesStarted) return null;
+        return Dependency.get(PluginActivityManager.class).instantiate(cl, className, intent);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginActivityManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginActivityManager.java
new file mode 100644
index 0000000..9becc38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginActivityManager.java
@@ -0,0 +1,43 @@
+/*
+ * 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 android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+
+public class PluginActivityManager {
+
+    private final Context mContext;
+    private final PluginManager mPluginManager;
+    private final ArrayMap<String, String> mActionLookup = new ArrayMap<>();
+
+    public PluginActivityManager(Context context, PluginManager pluginManager) {
+        mContext = context;
+        mPluginManager = pluginManager;
+    }
+
+    public void addActivityPlugin(String className, String action) {
+        mActionLookup.put(className, action);
+    }
+
+    public Activity instantiate(ClassLoader cl, String className, Intent intent) {
+        String action = mActionLookup.get(className);
+        if (TextUtils.isEmpty(action)) return null;
+        return mPluginManager.getOneShotPlugin(action, PluginActivity.class);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
index 493d244..a968399 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
@@ -42,7 +42,6 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.systemui.Dependency;
 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;
@@ -120,14 +119,21 @@
         }
         PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, null,
                 false, mLooper, cls, this);
+        PluginListener<Plugin> listener = new PluginListener<Plugin>() {
+            @Override
+            public void onPluginConnected(Plugin plugin, Context pluginContext) { }
+        };
+        mPluginMap.put(listener, p);
         mPluginPrefs.addAction(action);
-        PluginInfo<T> info = p.getPlugin();
+        PluginInstanceManager.PluginInfo<T> info = p.getPlugin();
         if (info != null) {
             mOneShotPackages.add(info.mPackage);
             mHasOneShot = true;
             startListening();
+            mPluginMap.remove(listener);
             return info.mPlugin;
         }
+        mPluginMap.remove(listener);
         return null;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 9ba32b3..de2ace4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -43,11 +43,14 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.SystemUI;
+import com.android.systemui.plugins.PluginActivity;
+import com.android.systemui.plugins.PluginActivityManager;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
@@ -234,6 +237,8 @@
             registerWithSystemUser();
         }
         putComponent(Recents.class, this);
+        Dependency.get(PluginActivityManager.class).addActivityPlugin(RecentsImpl.RECENTS_ACTIVITY,
+                PluginActivity.ACTION_RECENTS);
     }
 
     @Override
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 b8e9fcd..bba982c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
@@ -26,8 +26,6 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.net.Uri;
-import android.support.test.annotation.UiThreadTest;
-import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -36,11 +34,10 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.annotations.ProvidesInterface;
 import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
 import com.android.systemui.plugins.PluginManagerImpl.PluginInstanceManagerFactory;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
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 0a83a89..d1b1c5b 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
@@ -14,7 +14,6 @@
 
 package com.android.systemui.utils.leaks;
 
-import android.content.Context;
 import android.testing.LeakCheck;
 
 import com.android.systemui.plugins.Plugin;