diff options
author | Dave Mankoff <mankoff@google.com> | 2020-02-18 16:14:53 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2020-02-18 16:14:53 +0000 |
commit | c43a9482b53125d51028a3f1198a33586295875c (patch) | |
tree | 22e7020b4374add4d0d67631fc1eb3771980020f | |
parent | f20b2ecd898a460ffab7bb7d1b4f0101b5b14469 (diff) | |
parent | f50191441a764aae6b1246ee4d767e779b97a708 (diff) |
Merge "Provide PipManager through injection."
15 files changed, 455 insertions, 329 deletions
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java index 51b263ebdb4e..7d544c9a9bd7 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java @@ -21,6 +21,7 @@ import com.android.systemui.dagger.DependencyProvider; import com.android.systemui.dagger.SystemServicesModule; import com.android.systemui.dagger.SystemUIModule; import com.android.systemui.dagger.SystemUIRootComponent; +import com.android.systemui.pip.phone.dagger.PipModule; import javax.inject.Singleton; @@ -32,6 +33,7 @@ import dagger.Component; CarComponentBinder.class, DependencyProvider.class, DependencyBinder.class, + PipModule.class, SystemUIFactory.ContextHolder.class, SystemServicesModule.class, SystemUIModule.class, diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml index bb4999453d4f..90ea5e203429 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 3bf5ad759267..12b9be11817a 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.SystemUIAppComponentFactory; 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 @@ import dagger.Component; 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 92aa02050d28..383d459b7ed1 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 cecdc9cbb4f3..599c845ba39a 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.UserHandle; 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 @@ public class PipUI extends SystemUI implements CommandQueue.Callbacks { 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 @@ public class PipUI extends SystemUI implements CommandQueue.Callbacks { 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 239ef3638ff6..b5c8d66947ca 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 com.android.systemui.wm.DisplayController; 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 @@ public class PipManager implements BasePipManager { } } - 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 @@ public class PipManager implements BasePipManager { 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 000000000000..c8b6982a2eba --- /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 a40b72b30f22..9c175bc2b756 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} - } - }); - } - - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - updateMediaController(); - mPipManager.addMediaListener(mPipMediaListener); - } - - @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; + PipControlButtonView getFullButtonView() { + return findViewById(R.id.full_button); } - /** - * Updates the set of activity-defined actions. - */ - public void setActions(List<RemoteAction> actions) { - mCustomActions.clear(); - mCustomActions.addAll(actions); - updateUserActions(); + PipControlButtonView getCloseButtonView() { + return findViewById(R.id.close_button); } - /** - * 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 000000000000..1fe531b372c6 --- /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 7532f9f11296..487c2533b0bb 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.ActivityManagerWrapper; 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 @@ public class PipManager implements BasePipManager { } } - 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 @@ public class PipManager implements BasePipManager { 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 @@ public class PipManager implements BasePipManager { 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 3a5fa2253057..f43f8e795fe6 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.content.pm.ParceledListSlice; 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 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { 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 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { 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 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @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 ca1513128e56..b01c2f4eb5fb 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 @@ public class PipNotification { 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 class PipNotification { } }; - 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 @@ public class PipNotification { .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 000000000000..52b38a91a58f --- /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 000000000000..8e8b7f37b8d6 --- /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 264ddc026781..be30a4a4c72e 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); |