Provide PipManager through injection.

Adds a controller for PipControlsView so that depdencies can be provided
via injection.

Adds a TvPipComponent to allow injection of PIP related views into related
classes.

Bug: 146660939
Test: atest SystemUITests
Change-Id: Ic1a8f8f0f8e5506f354bb1f07b179db1d7e89577
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index bb49994..90ea5e2 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -21,8 +21,9 @@
      for different hardware and product builds. -->
 <resources>
     <!-- SystemUIFactory component -->
-    <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.tv.TvSystemUIFactory</string>
-
+    <string name="config_systemUIFactoryComponent" translatable="false">
+        com.android.systemui.tv.TvSystemUIFactory
+    </string>
     <!-- SystemUI Services: The classes of the stuff to start. -->
     <string-array name="config_systemUIServiceComponents" translatable="false">
         <item>com.android.systemui.util.NotificationChannels</item>
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
index 3bf5ad7..12b9be1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
@@ -27,6 +27,7 @@
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.pip.phone.dagger.PipModule;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.InjectionInflationController;
 
@@ -43,6 +44,7 @@
         DefaultComponentBinder.class,
         DependencyProvider.class,
         DependencyBinder.class,
+        PipModule.class,
         SystemServicesModule.class,
         SystemUIFactory.ContextHolder.class,
         SystemUIBinder.class,
diff --git a/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java b/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java
index 92aa020..383d459 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java
@@ -16,17 +16,11 @@
 
 package com.android.systemui.pip;
 
-import android.content.Context;
 import android.content.res.Configuration;
 
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.wm.DisplayController;
-
 import java.io.PrintWriter;
 
 public interface BasePipManager {
-    void initialize(Context context, BroadcastDispatcher broadcastDispatcher,
-            DisplayController displayController);
     void showPictureInPictureMenu();
     default void expandPip() {}
     default void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
index cecdc9c..599c845 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.pip;
 
-import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
 
 import android.content.Context;
@@ -26,9 +25,7 @@
 import android.os.UserManager;
 
 import com.android.systemui.SystemUI;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.wm.DisplayController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -44,25 +41,20 @@
 
     private final CommandQueue mCommandQueue;
     private BasePipManager mPipManager;
-    private final BroadcastDispatcher mBroadcastDispatcher;
-    private final DisplayController mDisplayController;
-    private boolean mSupportsPip;
 
     @Inject
     public PipUI(Context context, CommandQueue commandQueue,
-            BroadcastDispatcher broadcastDispatcher,
-            DisplayController displayController) {
+            BasePipManager pipManager) {
         super(context);
-        mBroadcastDispatcher = broadcastDispatcher;
         mCommandQueue = commandQueue;
-        mDisplayController = displayController;
+        mPipManager = pipManager;
     }
 
     @Override
     public void start() {
         PackageManager pm = mContext.getPackageManager();
-        mSupportsPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
-        if (!mSupportsPip) {
+        boolean supportsPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
+        if (!supportsPip) {
             return;
         }
 
@@ -72,11 +64,6 @@
             throw new IllegalStateException("Non-primary Pip component not currently supported.");
         }
 
-        mPipManager = pm.hasSystemFeature(FEATURE_LEANBACK_ONLY)
-                ? com.android.systemui.pip.tv.PipManager.getInstance()
-                : com.android.systemui.pip.phone.PipManager.getInstance();
-        mPipManager.initialize(mContext, mBroadcastDispatcher, mDisplayController);
-
         mCommandQueue.addCallback(this);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 239ef36..b5c8d66 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -51,14 +51,16 @@
 
 import java.io.PrintWriter;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
 /**
  * Manages the picture-in-picture (PIP) UI and states for Phones.
  */
+@Singleton
 public class PipManager implements BasePipManager {
     private static final String TAG = "PipManager";
 
-    private static PipManager sPipController;
-
     private Context mContext;
     private IActivityManager mActivityManager;
     private IActivityTaskManager mActivityTaskManager;
@@ -225,12 +227,8 @@
         }
     }
 
-    private PipManager() {}
-
-    /**
-     * Initializes {@link PipManager}.
-     */
-    public void initialize(Context context, BroadcastDispatcher broadcastDispatcher,
+    @Inject
+    public PipManager(Context context, BroadcastDispatcher broadcastDispatcher,
             DisplayController displayController) {
         mContext = context;
         mActivityManager = ActivityManager.getService();
@@ -329,16 +327,6 @@
                 mTmpDisplayInfo.rotation);
     }
 
-    /**
-     * Gets an instance of {@link PipManager}.
-     */
-    public static PipManager getInstance() {
-        if (sPipController == null) {
-            sPipController = new PipManager();
-        }
-        return sPipController;
-    }
-
     public void dump(PrintWriter pw) {
         final String innerPrefix = "  ";
         pw.println(TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipModule.java b/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipModule.java
new file mode 100644
index 0000000..c8b6982
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/dagger/PipModule.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 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.pip.phone.dagger;
+
+import com.android.systemui.pip.BasePipManager;
+import com.android.systemui.pip.phone.PipManager;
+
+import dagger.Binds;
+import dagger.Module;
+
+/**
+ * Dagger Module for Phone PIP.
+ */
+@Module
+public abstract class PipModule {
+
+    /** Binds PipManager as the default BasePipManager. */
+    @Binds
+    public abstract BasePipManager providePipManager(PipManager pipManager);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
index a40b72b..9c175bc 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
@@ -16,286 +16,38 @@
 
 package com.android.systemui.pip.tv;
 
-import android.app.PendingIntent.CanceledException;
-import android.app.RemoteAction;
 import android.content.Context;
-import android.graphics.Color;
-import android.media.session.MediaController;
-import android.media.session.PlaybackState;
-import android.os.Handler;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
-import android.view.View;
 import android.widget.LinearLayout;
 
 import com.android.systemui.R;
 
-import java.util.ArrayList;
-import java.util.List;
-
 
 /**
  * A view containing PIP controls including fullscreen, close, and media controls.
  */
 public class PipControlsView extends LinearLayout {
 
-    private static final String TAG = PipControlsView.class.getSimpleName();
-
-    private static final float DISABLED_ACTION_ALPHA = 0.54f;
-
-    /**
-     * An interface to listen user action.
-     */
-    public abstract static interface Listener {
-        /**
-         * Called when an user clicks close PIP button.
-         */
-        public abstract void onClosed();
-    };
-
-    private MediaController mMediaController;
-
-    private final PipManager mPipManager = PipManager.getInstance();
-    private final LayoutInflater mLayoutInflater;
-    private final Handler mHandler;
-    private Listener mListener;
-
-    private PipControlButtonView mFullButtonView;
-    private PipControlButtonView mCloseButtonView;
-    private PipControlButtonView mPlayPauseButtonView;
-    private ArrayList<PipControlButtonView> mCustomButtonViews = new ArrayList<>();
-    private List<RemoteAction> mCustomActions = new ArrayList<>();
-
-    private PipControlButtonView mFocusedChild;
-
-    private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
-        @Override
-        public void onPlaybackStateChanged(PlaybackState state) {
-            updateUserActions();
-        }
-    };
-
-    private final PipManager.MediaListener mPipMediaListener = new PipManager.MediaListener() {
-        @Override
-        public void onMediaControllerChanged() {
-            updateMediaController();
-        }
-    };
-
-    private final OnFocusChangeListener mFocusChangeListener = new OnFocusChangeListener() {
-        @Override
-        public void onFocusChange(View view, boolean hasFocus) {
-            if (hasFocus) {
-                mFocusedChild = (PipControlButtonView) view;
-            } else if (mFocusedChild == view) {
-                mFocusedChild = null;
-            }
-        }
-    };
-
-    public PipControlsView(Context context) {
-        this(context, null, 0, 0);
-    }
-
-    public PipControlsView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0, 0);
-    }
-
-    public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
     public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mLayoutInflater = (LayoutInflater) getContext()
-                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        mLayoutInflater.inflate(R.layout.tv_pip_controls, this);
-        mHandler = new Handler();
-
+        LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+        layoutInflater.inflate(R.layout.tv_pip_controls, this);
         setOrientation(LinearLayout.HORIZONTAL);
         setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
     }
 
-    @Override
-    public void onFinishInflate() {
-        super.onFinishInflate();
-
-        mFullButtonView = findViewById(R.id.full_button);
-        mFullButtonView.setOnFocusChangeListener(mFocusChangeListener);
-        mFullButtonView.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mPipManager.movePipToFullscreen();
-            }
-        });
-
-        mCloseButtonView = findViewById(R.id.close_button);
-        mCloseButtonView.setOnFocusChangeListener(mFocusChangeListener);
-        mCloseButtonView.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mPipManager.closePip();
-                if (mListener != null) {
-                    mListener.onClosed();
-                }
-            }
-        });
-
-        mPlayPauseButtonView = findViewById(R.id.play_pause_button);
-        mPlayPauseButtonView.setOnFocusChangeListener(mFocusChangeListener);
-        mPlayPauseButtonView.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                if (mMediaController == null || mMediaController.getPlaybackState() == null) {
-                    return;
-                }
-                long actions = mMediaController.getPlaybackState().getActions();
-                int state = mMediaController.getPlaybackState().getState();
-                if (mPipManager.getPlaybackState() == PipManager.PLAYBACK_STATE_PAUSED) {
-                    mMediaController.getTransportControls().play();
-                } else if (mPipManager.getPlaybackState() == PipManager.PLAYBACK_STATE_PLAYING) {
-                    mMediaController.getTransportControls().pause();
-                }
-                // View will be updated later in {@link mMediaControllerCallback}
-            }
-        });
+    PipControlButtonView getFullButtonView() {
+        return findViewById(R.id.full_button);
     }
 
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        updateMediaController();
-        mPipManager.addMediaListener(mPipMediaListener);
+    PipControlButtonView getCloseButtonView() {
+        return findViewById(R.id.close_button);
     }
 
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mPipManager.removeMediaListener(mPipMediaListener);
-        if (mMediaController != null) {
-            mMediaController.unregisterCallback(mMediaControllerCallback);
-        }
-    }
-
-    private void updateMediaController() {
-        MediaController newController = mPipManager.getMediaController();
-        if (mMediaController == newController) {
-            return;
-        }
-        if (mMediaController != null) {
-            mMediaController.unregisterCallback(mMediaControllerCallback);
-        }
-        mMediaController = newController;
-        if (mMediaController != null) {
-            mMediaController.registerCallback(mMediaControllerCallback);
-        }
-        updateUserActions();
-    }
-
-    /**
-     * Updates the actions for the PIP. If there are no custom actions, then the media session
-     * actions are shown.
-     */
-    private void updateUserActions() {
-        if (!mCustomActions.isEmpty()) {
-            // Ensure we have as many buttons as actions
-            while (mCustomButtonViews.size() < mCustomActions.size()) {
-                PipControlButtonView buttonView = (PipControlButtonView) mLayoutInflater.inflate(
-                        R.layout.tv_pip_custom_control, this, false);
-                addView(buttonView);
-                mCustomButtonViews.add(buttonView);
-            }
-
-            // Update the visibility of all views
-            for (int i = 0; i < mCustomButtonViews.size(); i++) {
-                mCustomButtonViews.get(i).setVisibility(i < mCustomActions.size()
-                        ? View.VISIBLE
-                        : View.GONE);
-            }
-
-            // Update the state and visibility of the action buttons, and hide the rest
-            for (int i = 0; i < mCustomActions.size(); i++) {
-                final RemoteAction action = mCustomActions.get(i);
-                PipControlButtonView actionView = mCustomButtonViews.get(i);
-
-                // TODO: Check if the action drawable has changed before we reload it
-                action.getIcon().loadDrawableAsync(getContext(), d -> {
-                    d.setTint(Color.WHITE);
-                    actionView.setImageDrawable(d);
-                }, mHandler);
-                actionView.setText(action.getContentDescription());
-                if (action.isEnabled()) {
-                    actionView.setOnClickListener(v -> {
-                        try {
-                            action.getActionIntent().send();
-                        } catch (CanceledException e) {
-                            Log.w(TAG, "Failed to send action", e);
-                        }
-                    });
-                }
-                actionView.setEnabled(action.isEnabled());
-                actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
-            }
-
-            // Hide the media session buttons
-            mPlayPauseButtonView.setVisibility(View.GONE);
-        } else {
-            int state = mPipManager.getPlaybackState();
-            if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) {
-                mPlayPauseButtonView.setVisibility(View.GONE);
-            } else {
-                mPlayPauseButtonView.setVisibility(View.VISIBLE);
-                if (state == PipManager.PLAYBACK_STATE_PLAYING) {
-                    mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white);
-                    mPlayPauseButtonView.setText(R.string.pip_pause);
-                } else {
-                    mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white);
-                    mPlayPauseButtonView.setText(R.string.pip_play);
-                }
-            }
-
-            // Hide all the custom action buttons
-            for (int i = 0; i < mCustomButtonViews.size(); i++) {
-                mCustomButtonViews.get(i).setVisibility(View.GONE);
-            }
-        }
-    }
-
-    /**
-     * Resets to initial state.
-     */
-    public void reset() {
-        mFullButtonView.reset();
-        mCloseButtonView.reset();
-        mPlayPauseButtonView.reset();
-        mFullButtonView.requestFocus();
-        for (int i = 0; i < mCustomButtonViews.size(); i++) {
-            mCustomButtonViews.get(i).reset();
-        }
-    }
-
-    /**
-     * Sets the {@link Listener} to listen user actions.
-     */
-    public void setListener(Listener listener) {
-        mListener = listener;
-    }
-
-    /**
-     * Updates the set of activity-defined actions.
-     */
-    public void setActions(List<RemoteAction> actions) {
-        mCustomActions.clear();
-        mCustomActions.addAll(actions);
-        updateUserActions();
-    }
-
-    /**
-     * Returns the focused control button view to animate focused button.
-     */
-    PipControlButtonView getFocusedButton() {
-        return mFocusedChild;
+    PipControlButtonView getPlayPauseButtonView() {
+        return findViewById(R.id.play_pause_button);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsViewController.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsViewController.java
new file mode 100644
index 0000000..1fe531b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsViewController.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2019 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.pip.tv;
+
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.graphics.Color;
+import android.media.session.MediaController;
+import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * Controller for {@link PipControlsView}.
+ */
+public class PipControlsViewController {
+    private static final String TAG = PipControlsViewController.class.getSimpleName();
+
+    private static final float DISABLED_ACTION_ALPHA = 0.54f;
+
+    private final PipControlsView mView;
+    private final PipManager mPipManager;
+    private final LayoutInflater mLayoutInflater;
+    private final Handler mHandler;
+    private final PipControlButtonView mPlayPauseButtonView;
+    private MediaController mMediaController;
+    private PipControlButtonView mFocusedChild;
+    private Listener mListener;
+    private ArrayList<PipControlButtonView> mCustomButtonViews = new ArrayList<>();
+    private List<RemoteAction> mCustomActions = new ArrayList<>();
+
+    public PipControlsView getView() {
+        return mView;
+    }
+
+    /**
+     * An interface to listen user action.
+     */
+    public interface Listener {
+        /**
+         * Called when a user clicks close PIP button.
+         */
+        void onClosed();
+    }
+
+    private View.OnAttachStateChangeListener
+            mOnAttachStateChangeListener =
+            new View.OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View v) {
+                    updateMediaController();
+                    mPipManager.addMediaListener(mPipMediaListener);
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                    mPipManager.removeMediaListener(mPipMediaListener);
+                }
+            };
+
+    private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
+        @Override
+        public void onPlaybackStateChanged(PlaybackState state) {
+            updateUserActions();
+        }
+    };
+
+    private final PipManager.MediaListener mPipMediaListener = this::updateMediaController;
+
+    private final View.OnFocusChangeListener
+            mFocusChangeListener =
+            new View.OnFocusChangeListener() {
+                @Override
+                public void onFocusChange(View view, boolean hasFocus) {
+                    if (hasFocus) {
+                        mFocusedChild = (PipControlButtonView) view;
+                    } else if (mFocusedChild == view) {
+                        mFocusedChild = null;
+                    }
+                }
+            };
+
+
+    @Inject
+    public PipControlsViewController(PipControlsView view, PipManager pipManager,
+            LayoutInflater layoutInflater, @Main Handler handler) {
+        super();
+        mView = view;
+        mPipManager = pipManager;
+        mLayoutInflater = layoutInflater;
+        mHandler = handler;
+
+        mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+        if (mView.isAttachedToWindow()) {
+            mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
+        }
+
+        View fullButtonView = mView.getFullButtonView();
+        fullButtonView.setOnFocusChangeListener(mFocusChangeListener);
+        fullButtonView.setOnClickListener(v -> mPipManager.movePipToFullscreen());
+
+        View closeButtonView = mView.getCloseButtonView();
+        closeButtonView.setOnFocusChangeListener(mFocusChangeListener);
+        closeButtonView.setOnClickListener(v -> {
+            mPipManager.closePip();
+            if (mListener != null) {
+                mListener.onClosed();
+            }
+        });
+
+
+        mPlayPauseButtonView = mView.getPlayPauseButtonView();
+        mPlayPauseButtonView.setOnFocusChangeListener(mFocusChangeListener);
+        mPlayPauseButtonView.setOnClickListener(v -> {
+            if (mMediaController == null || mMediaController.getPlaybackState() == null) {
+                return;
+            }
+            if (mPipManager.getPlaybackState() == PipManager.PLAYBACK_STATE_PAUSED) {
+                mMediaController.getTransportControls().play();
+            } else if (mPipManager.getPlaybackState() == PipManager.PLAYBACK_STATE_PLAYING) {
+                mMediaController.getTransportControls().pause();
+            }
+            // View will be updated later in {@link mMediaControllerCallback}
+        });
+    }
+
+    private void updateMediaController() {
+        MediaController newController = mPipManager.getMediaController();
+        if (mMediaController == newController) {
+            return;
+        }
+        if (mMediaController != null) {
+            mMediaController.unregisterCallback(mMediaControllerCallback);
+        }
+        mMediaController = newController;
+        if (mMediaController != null) {
+            mMediaController.registerCallback(mMediaControllerCallback);
+        }
+        updateUserActions();
+    }
+
+    /**
+     * Updates the actions for the PIP. If there are no custom actions, then the media session
+     * actions are shown.
+     */
+    private void updateUserActions() {
+        if (!mCustomActions.isEmpty()) {
+            // Ensure we have as many buttons as actions
+            while (mCustomButtonViews.size() < mCustomActions.size()) {
+                PipControlButtonView buttonView = (PipControlButtonView) mLayoutInflater.inflate(
+                        R.layout.tv_pip_custom_control, mView, false);
+                mView.addView(buttonView);
+                mCustomButtonViews.add(buttonView);
+            }
+
+            // Update the visibility of all views
+            for (int i = 0; i < mCustomButtonViews.size(); i++) {
+                mCustomButtonViews.get(i).setVisibility(
+                        i < mCustomActions.size() ? View.VISIBLE : View.GONE);
+            }
+
+            // Update the state and visibility of the action buttons, and hide the rest
+            for (int i = 0; i < mCustomActions.size(); i++) {
+                final RemoteAction action = mCustomActions.get(i);
+                PipControlButtonView actionView = mCustomButtonViews.get(i);
+
+                // TODO: Check if the action drawable has changed before we reload it
+                action.getIcon().loadDrawableAsync(mView.getContext(), d -> {
+                    d.setTint(Color.WHITE);
+                    actionView.setImageDrawable(d);
+                }, mHandler);
+                actionView.setText(action.getContentDescription());
+                if (action.isEnabled()) {
+                    actionView.setOnClickListener(v -> {
+                        try {
+                            action.getActionIntent().send();
+                        } catch (PendingIntent.CanceledException e) {
+                            Log.w(TAG, "Failed to send action", e);
+                        }
+                    });
+                }
+                actionView.setEnabled(action.isEnabled());
+                actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
+            }
+
+            // Hide the media session buttons
+            mPlayPauseButtonView.setVisibility(View.GONE);
+        } else {
+            int state = mPipManager.getPlaybackState();
+            if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) {
+                mPlayPauseButtonView.setVisibility(View.GONE);
+            } else {
+                mPlayPauseButtonView.setVisibility(View.VISIBLE);
+                if (state == PipManager.PLAYBACK_STATE_PLAYING) {
+                    mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white);
+                    mPlayPauseButtonView.setText(R.string.pip_pause);
+                } else {
+                    mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white);
+                    mPlayPauseButtonView.setText(R.string.pip_play);
+                }
+            }
+
+            // Hide all the custom action buttons
+            for (int i = 0; i < mCustomButtonViews.size(); i++) {
+                mCustomButtonViews.get(i).setVisibility(View.GONE);
+            }
+        }
+    }
+
+
+    /**
+     * Sets the {@link Listener} to listen user actions.
+     */
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+
+    /**
+     * Updates the set of activity-defined actions.
+     */
+    public void setActions(List<RemoteAction> actions) {
+        mCustomActions.clear();
+        mCustomActions.addAll(actions);
+        updateUserActions();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 7532f9f..487c253 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -55,21 +55,23 @@
 import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
-import com.android.systemui.wm.DisplayController;
 
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
 /**
  * Manages the picture-in-picture (PIP) UI and states.
  */
+@Singleton
 public class PipManager implements BasePipManager {
     private static final String TAG = "PipManager";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final String SETTINGS_PACKAGE_AND_CLASS_DELIMITER = "/";
 
-    private static PipManager sPipManager;
     private static List<Pair<String, String>> sSettingsPackageAndClassNamePairList;
 
     /**
@@ -224,13 +226,8 @@
         }
     }
 
-    private PipManager() { }
-
-    /**
-     * Initializes {@link PipManager}.
-     */
-    public void initialize(Context context, BroadcastDispatcher broadcastDispatcher,
-            DisplayController displayController) {
+    @Inject
+    public PipManager(Context context, BroadcastDispatcher broadcastDispatcher) {
         if (mInitialized) {
             return;
         }
@@ -289,7 +286,7 @@
             Log.e(TAG, "Failed to register pinned stack listener", e);
         }
 
-        mPipNotification = new PipNotification(context, broadcastDispatcher);
+        mPipNotification = new PipNotification(context, broadcastDispatcher, this);
     }
 
     private void loadConfigurationsAndApply(Configuration newConfig) {
@@ -739,16 +736,6 @@
         void onMediaControllerChanged();
     }
 
-    /**
-     * Gets an instance of {@link PipManager}.
-     */
-    public static PipManager getInstance() {
-        if (sPipManager == null) {
-            sPipManager = new PipManager();
-        }
-        return sPipManager;
-    }
-
     private void updatePipVisibility(final boolean visible) {
         Dependency.get(UiOffloadThread.class).execute(() -> {
             WindowManagerWrapper.getInstance().setPipVisibility(visible);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
index 3a5fa22..f43f8e7 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
@@ -24,8 +24,12 @@
 import android.os.Bundle;
 
 import com.android.systemui.R;
+import com.android.systemui.pip.tv.dagger.TvPipComponent;
 
 import java.util.Collections;
+
+import javax.inject.Inject;
+
 /**
  * Activity to show the PIP menu to control PIP.
  */
@@ -34,12 +38,22 @@
 
     static final String EXTRA_CUSTOM_ACTIONS = "custom_actions";
 
-    private final PipManager mPipManager = PipManager.getInstance();
+    private final TvPipComponent.Builder mPipComponentBuilder;
+    private TvPipComponent mTvPipComponent;
+    private final PipManager mPipManager;
 
     private Animator mFadeInAnimation;
     private Animator mFadeOutAnimation;
-    private PipControlsView mPipControlsView;
     private boolean mRestorePipSizeWhenClose;
+    private PipControlsViewController mPipControlsViewController;
+
+
+    @Inject
+    public PipMenuActivity(TvPipComponent.Builder pipComponentBuilder, PipManager pipManager) {
+        super();
+        mPipComponentBuilder = pipComponentBuilder;
+        mPipManager = pipManager;
+    }
 
     @Override
     protected void onCreate(Bundle bundle) {
@@ -48,16 +62,19 @@
             finish();
         }
         setContentView(R.layout.tv_pip_menu);
+        mTvPipComponent = mPipComponentBuilder.pipControlsView(
+                findViewById(R.id.pip_controls)).build();
+        mPipControlsViewController = mTvPipComponent.getPipControlsViewController();
+
         mPipManager.addListener(this);
 
         mRestorePipSizeWhenClose = true;
-        mPipControlsView = findViewById(R.id.pip_controls);
         mFadeInAnimation = AnimatorInflater.loadAnimator(
                 this, R.anim.tv_pip_menu_fade_in_animation);
-        mFadeInAnimation.setTarget(mPipControlsView);
+        mFadeInAnimation.setTarget(mPipControlsViewController.getView());
         mFadeOutAnimation = AnimatorInflater.loadAnimator(
                 this, R.anim.tv_pip_menu_fade_out_animation);
-        mFadeOutAnimation.setTarget(mPipControlsView);
+        mFadeOutAnimation.setTarget(mPipControlsViewController.getView());
 
         onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS));
     }
@@ -114,7 +131,8 @@
     @Override
     public void onPipMenuActionsChanged(ParceledListSlice actions) {
         boolean hasCustomActions = actions != null && !actions.getList().isEmpty();
-        mPipControlsView.setActions(hasCustomActions ? actions.getList() : Collections.EMPTY_LIST);
+        mPipControlsViewController.setActions(
+                hasCustomActions ? actions.getList() : Collections.EMPTY_LIST);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
index ca15131..b01c2f4 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
@@ -50,7 +50,7 @@
     private static final String ACTION_MENU = "PipNotification.menu";
     private static final String ACTION_CLOSE = "PipNotification.close";
 
-    private final PipManager mPipManager = PipManager.getInstance();
+    private final PipManager mPipManager;
 
     private final NotificationManager mNotificationManager;
     private final Notification.Builder mNotificationBuilder;
@@ -144,7 +144,8 @@
         }
     };
 
-    public PipNotification(Context context, BroadcastDispatcher broadcastDispatcher) {
+    public PipNotification(Context context, BroadcastDispatcher broadcastDispatcher,
+            PipManager pipManager) {
         mNotificationManager = (NotificationManager) context.getSystemService(
                 Context.NOTIFICATION_SERVICE);
 
@@ -156,6 +157,7 @@
                         .setContentIntent(createPendingIntent(context, ACTION_MENU))
                         .setDeleteIntent(createPendingIntent(context, ACTION_CLOSE)));
 
+        mPipManager = pipManager;
         mPipManager.addListener(mPipListener);
         mPipManager.addMediaListener(mPipMediaListener);
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/PipModule.java b/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/PipModule.java
new file mode 100644
index 0000000..52b38a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/PipModule.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 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.pip.tv.dagger;
+
+import android.app.Activity;
+
+import com.android.systemui.pip.BasePipManager;
+import com.android.systemui.pip.tv.PipManager;
+import com.android.systemui.pip.tv.PipMenuActivity;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+/**
+ * Dagger module for TV Pip.
+ */
+@Module(subcomponents = {TvPipComponent.class})
+public abstract class PipModule {
+
+    /** Binds PipManager as the default BasePipManager. */
+    @Binds
+    public abstract BasePipManager providePipManager(PipManager pipManager);
+
+
+    /** Inject into PipMenuActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(PipMenuActivity.class)
+    public abstract Activity providePipMenuActivity(PipMenuActivity activity);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipComponent.java b/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipComponent.java
new file mode 100644
index 0000000..8e8b7f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipComponent.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 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.pip.tv.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.pip.tv.PipControlsView;
+import com.android.systemui.pip.tv.PipControlsViewController;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Component for injecting into Pip related classes.
+ */
+@Subcomponent
+public interface TvPipComponent {
+    /**
+     * Builder for {@link StatusBarComponent}.
+     */
+    @Subcomponent.Builder
+    interface Builder {
+        @BindsInstance
+        TvPipComponent.Builder pipControlsView(PipControlsView pipControlsView);
+        TvPipComponent build();
+    }
+
+    /**
+     * Scope annotation for singleton items within the PipComponent.
+     */
+    @Documented
+    @Retention(RUNTIME)
+    @Scope
+    @interface PipScope {}
+
+    /**
+     * Creates a StatusBarWindowViewController.
+     */
+    @TvPipComponent.PipScope
+    PipControlsViewController getPipControlsViewController();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
index 264ddc0..be30a4a 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
@@ -17,11 +17,12 @@
 package com.android.systemui.tv;
 
 import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.pip.tv.dagger.PipModule;
 
 import dagger.Binds;
 import dagger.Module;
 
-@Module
+@Module(includes = {PipModule.class})
 interface TvSystemUIBinder {
     @Binds
     SystemUIRootComponent bindSystemUIRootComponent(TvSystemUIRootComponent systemUIRootComponent);