Plugin fragment support

Allows fragments to be easily switched over to plugins and a provides
a convenient base class for plugins to use that makes sure the layout
inflater and context point at the plugin's and not sysui's.

Bug: 32609190
Test: runtest systemui

Change-Id: I6503947e980f66ddcd826f6ca9a92b591ce0eb1e
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index 634e597..5f27b74 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -32,6 +32,7 @@
 
 import com.android.settingslib.applications.InterestingConfigChanges;
 import com.android.systemui.SystemUIApplication;
+import com.android.systemui.plugins.PluginManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -51,7 +52,7 @@
     private FragmentLifecycleCallbacks mLifecycleCallbacks;
 
     FragmentHostManager(Context context, FragmentService manager, View rootView) {
-        mContext = context;
+        mContext = PluginManager.getInstance(context).getAllPluginContext(context);
         mManager = manager;
         mRootView = rootView;
         mConfigChanges.applyNewConfig(context.getResources());
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
new file mode 100644
index 0000000..e107fcd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 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.fragments;
+
+import android.app.Fragment;
+import android.util.Log;
+import android.view.View;
+
+import com.android.systemui.plugins.FragmentBase;
+import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
+
+public class PluginFragmentListener implements PluginListener<Plugin> {
+
+    private static final String TAG = "PluginFragmentListener";
+
+    private final FragmentHostManager mFragmentHostManager;
+    private final PluginManager mPluginManager;
+    private final Class<? extends Fragment> mDefaultClass;
+    private final int mId;
+    private final String mTag;
+    private final Class<? extends FragmentBase> mExpectedInterface;
+
+    public PluginFragmentListener(View view, String tag, int id,
+            Class<? extends Fragment> defaultFragment,
+            Class<? extends FragmentBase> expectedInterface) {
+        mFragmentHostManager = FragmentHostManager.get(view);
+        mPluginManager = PluginManager.getInstance(view.getContext());
+        mExpectedInterface = expectedInterface;
+        mTag = tag;
+        mDefaultClass = defaultFragment;
+        mId = id;
+    }
+
+    public void startListening(String action, int version) {
+        try {
+            setFragment(mDefaultClass.newInstance());
+        } catch (InstantiationException | IllegalAccessException e) {
+            Log.e(TAG, "Couldn't instantiate " + mDefaultClass.getName(), e);
+        }
+        mPluginManager.addPluginListener(action, this, version, false /* Only allow one */);
+    }
+
+    public void stopListening() {
+        mPluginManager.removePluginListener(this);
+    }
+
+    private void setFragment(Fragment fragment) {
+        mFragmentHostManager.getFragmentManager().beginTransaction()
+                .replace(mId, fragment, mTag)
+                .commit();
+    }
+
+    @Override
+    public void onPluginConnected(Plugin plugin) {
+        try {
+            mExpectedInterface.cast(plugin);
+            setFragment((Fragment) plugin);
+        } catch (ClassCastException e) {
+            Log.e(TAG, plugin.getClass().getName() + " must be a Fragment and implement "
+                    + mExpectedInterface.getName(), e);
+        }
+    }
+
+    @Override
+    public void onPluginDisconnected(Plugin plugin) {
+        try {
+            setFragment(mDefaultClass.newInstance());
+        } catch (InstantiationException | IllegalAccessException e) {
+            Log.e(TAG, "Couldn't instantiate " + mDefaultClass.getName(), e);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 749b34e..69d76e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -1621,6 +1621,7 @@
         }
         // Since there are QS tiles in the header now, we need to make sure we start listening
         // immediately so they can be up to date.
+        if (mQs == null) return;
         mQs.setHeaderListening(true);
     }
 
@@ -1749,7 +1750,7 @@
     }
 
     public void onQsHeightChanged() {
-        mQsMaxExpansionHeight = mQs.getDesiredHeight();
+        mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
         if (mQsExpanded && mQsFullyExpanded) {
             mQsExpansionHeight = mQsMaxExpansionHeight;
             requestScrollerTopPaddingUpdate(false /* animate */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 197fe24..471cb32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -90,7 +90,6 @@
 import android.os.UserManager;
 import android.os.Vibrator;
 import android.provider.Settings;
-import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
 import android.telecom.TelecomManager;
@@ -126,8 +125,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.AutoReinflateContainer;
-import com.android.systemui.AutoReinflateContainer.InflateListener;
 import com.android.systemui.BatteryMeterView;
 import com.android.systemui.DemoMode;
 import com.android.systemui.EventLogConstants;
@@ -142,11 +139,11 @@
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.PluginFragmentListener;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.qs.QS.ActivityStarter;
 import com.android.systemui.plugins.qs.QS.BaseStatusBarHeader;
-import com.android.systemui.qs.QSContainerImpl;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.recents.ScreenPinningRequest;
@@ -928,9 +925,8 @@
         View container = mStatusBarWindow.findViewById(R.id.qs_frame);
         if (container != null) {
             FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
-            fragmentHostManager.getFragmentManager().beginTransaction()
-                    .replace(R.id.qs_frame, new QSFragment(), QS.TAG)
-                    .commit();
+            new PluginFragmentListener(container, QS.TAG, R.id.qs_frame, QSFragment.class, QS.class)
+                    .startListening(QS.ACTION, QS.VERSION);
             final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
                     mBluetoothController, mLocationController, mRotationLockController,
                     mNetworkController, mZenModeController, mHotspotController,