blob: 1fe531b372c6812a36364e000ff917e138c6e029 [file] [log] [blame]
Dave Mankofff5019142019-12-20 16:22:57 -05001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.pip.tv;
18
19import android.app.PendingIntent;
20import android.app.RemoteAction;
21import android.graphics.Color;
22import android.media.session.MediaController;
23import android.media.session.PlaybackState;
24import android.os.Handler;
25import android.util.Log;
26import android.view.LayoutInflater;
27import android.view.View;
28
29import com.android.systemui.R;
30import com.android.systemui.dagger.qualifiers.Main;
31
32import java.util.ArrayList;
33import java.util.List;
34
35import javax.inject.Inject;
36
37/**
38 * Controller for {@link PipControlsView}.
39 */
40public class PipControlsViewController {
41 private static final String TAG = PipControlsViewController.class.getSimpleName();
42
43 private static final float DISABLED_ACTION_ALPHA = 0.54f;
44
45 private final PipControlsView mView;
46 private final PipManager mPipManager;
47 private final LayoutInflater mLayoutInflater;
48 private final Handler mHandler;
49 private final PipControlButtonView mPlayPauseButtonView;
50 private MediaController mMediaController;
51 private PipControlButtonView mFocusedChild;
52 private Listener mListener;
53 private ArrayList<PipControlButtonView> mCustomButtonViews = new ArrayList<>();
54 private List<RemoteAction> mCustomActions = new ArrayList<>();
55
56 public PipControlsView getView() {
57 return mView;
58 }
59
60 /**
61 * An interface to listen user action.
62 */
63 public interface Listener {
64 /**
65 * Called when a user clicks close PIP button.
66 */
67 void onClosed();
68 }
69
70 private View.OnAttachStateChangeListener
71 mOnAttachStateChangeListener =
72 new View.OnAttachStateChangeListener() {
73 @Override
74 public void onViewAttachedToWindow(View v) {
75 updateMediaController();
76 mPipManager.addMediaListener(mPipMediaListener);
77 }
78
79 @Override
80 public void onViewDetachedFromWindow(View v) {
81 mPipManager.removeMediaListener(mPipMediaListener);
82 }
83 };
84
85 private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
86 @Override
87 public void onPlaybackStateChanged(PlaybackState state) {
88 updateUserActions();
89 }
90 };
91
92 private final PipManager.MediaListener mPipMediaListener = this::updateMediaController;
93
94 private final View.OnFocusChangeListener
95 mFocusChangeListener =
96 new View.OnFocusChangeListener() {
97 @Override
98 public void onFocusChange(View view, boolean hasFocus) {
99 if (hasFocus) {
100 mFocusedChild = (PipControlButtonView) view;
101 } else if (mFocusedChild == view) {
102 mFocusedChild = null;
103 }
104 }
105 };
106
107
108 @Inject
109 public PipControlsViewController(PipControlsView view, PipManager pipManager,
110 LayoutInflater layoutInflater, @Main Handler handler) {
111 super();
112 mView = view;
113 mPipManager = pipManager;
114 mLayoutInflater = layoutInflater;
115 mHandler = handler;
116
117 mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
118 if (mView.isAttachedToWindow()) {
119 mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
120 }
121
122 View fullButtonView = mView.getFullButtonView();
123 fullButtonView.setOnFocusChangeListener(mFocusChangeListener);
124 fullButtonView.setOnClickListener(v -> mPipManager.movePipToFullscreen());
125
126 View closeButtonView = mView.getCloseButtonView();
127 closeButtonView.setOnFocusChangeListener(mFocusChangeListener);
128 closeButtonView.setOnClickListener(v -> {
129 mPipManager.closePip();
130 if (mListener != null) {
131 mListener.onClosed();
132 }
133 });
134
135
136 mPlayPauseButtonView = mView.getPlayPauseButtonView();
137 mPlayPauseButtonView.setOnFocusChangeListener(mFocusChangeListener);
138 mPlayPauseButtonView.setOnClickListener(v -> {
139 if (mMediaController == null || mMediaController.getPlaybackState() == null) {
140 return;
141 }
142 if (mPipManager.getPlaybackState() == PipManager.PLAYBACK_STATE_PAUSED) {
143 mMediaController.getTransportControls().play();
144 } else if (mPipManager.getPlaybackState() == PipManager.PLAYBACK_STATE_PLAYING) {
145 mMediaController.getTransportControls().pause();
146 }
147 // View will be updated later in {@link mMediaControllerCallback}
148 });
149 }
150
151 private void updateMediaController() {
152 MediaController newController = mPipManager.getMediaController();
153 if (mMediaController == newController) {
154 return;
155 }
156 if (mMediaController != null) {
157 mMediaController.unregisterCallback(mMediaControllerCallback);
158 }
159 mMediaController = newController;
160 if (mMediaController != null) {
161 mMediaController.registerCallback(mMediaControllerCallback);
162 }
163 updateUserActions();
164 }
165
166 /**
167 * Updates the actions for the PIP. If there are no custom actions, then the media session
168 * actions are shown.
169 */
170 private void updateUserActions() {
171 if (!mCustomActions.isEmpty()) {
172 // Ensure we have as many buttons as actions
173 while (mCustomButtonViews.size() < mCustomActions.size()) {
174 PipControlButtonView buttonView = (PipControlButtonView) mLayoutInflater.inflate(
175 R.layout.tv_pip_custom_control, mView, false);
176 mView.addView(buttonView);
177 mCustomButtonViews.add(buttonView);
178 }
179
180 // Update the visibility of all views
181 for (int i = 0; i < mCustomButtonViews.size(); i++) {
182 mCustomButtonViews.get(i).setVisibility(
183 i < mCustomActions.size() ? View.VISIBLE : View.GONE);
184 }
185
186 // Update the state and visibility of the action buttons, and hide the rest
187 for (int i = 0; i < mCustomActions.size(); i++) {
188 final RemoteAction action = mCustomActions.get(i);
189 PipControlButtonView actionView = mCustomButtonViews.get(i);
190
191 // TODO: Check if the action drawable has changed before we reload it
192 action.getIcon().loadDrawableAsync(mView.getContext(), d -> {
193 d.setTint(Color.WHITE);
194 actionView.setImageDrawable(d);
195 }, mHandler);
196 actionView.setText(action.getContentDescription());
197 if (action.isEnabled()) {
198 actionView.setOnClickListener(v -> {
199 try {
200 action.getActionIntent().send();
201 } catch (PendingIntent.CanceledException e) {
202 Log.w(TAG, "Failed to send action", e);
203 }
204 });
205 }
206 actionView.setEnabled(action.isEnabled());
207 actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
208 }
209
210 // Hide the media session buttons
211 mPlayPauseButtonView.setVisibility(View.GONE);
212 } else {
213 int state = mPipManager.getPlaybackState();
214 if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) {
215 mPlayPauseButtonView.setVisibility(View.GONE);
216 } else {
217 mPlayPauseButtonView.setVisibility(View.VISIBLE);
218 if (state == PipManager.PLAYBACK_STATE_PLAYING) {
219 mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white);
220 mPlayPauseButtonView.setText(R.string.pip_pause);
221 } else {
222 mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white);
223 mPlayPauseButtonView.setText(R.string.pip_play);
224 }
225 }
226
227 // Hide all the custom action buttons
228 for (int i = 0; i < mCustomButtonViews.size(); i++) {
229 mCustomButtonViews.get(i).setVisibility(View.GONE);
230 }
231 }
232 }
233
234
235 /**
236 * Sets the {@link Listener} to listen user actions.
237 */
238 public void setListener(Listener listener) {
239 mListener = listener;
240 }
241
242
243 /**
244 * Updates the set of activity-defined actions.
245 */
246 public void setActions(List<RemoteAction> actions) {
247 mCustomActions.clear();
248 mCustomActions.addAll(actions);
249 updateUserActions();
250 }
251}