1 /*
2  * Copyright (C) 2020 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 
17 package com.android.wm.shell.pip.tv;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorInflater;
21 import android.content.Context;
22 import android.content.res.TypedArray;
23 import android.graphics.drawable.Drawable;
24 import android.util.AttributeSet;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.widget.ImageView;
28 import android.widget.RelativeLayout;
29 import android.widget.TextView;
30 
31 import com.android.wm.shell.R;
32 
33 /**
34  * A View that represents Pip Menu action button, such as "Fullscreen" and "Close" as well custom
35  * (provided by the application in Pip) and media buttons.
36  */
37 public class TvPipMenuActionButton extends RelativeLayout implements View.OnClickListener {
38     private final ImageView mIconImageView;
39     private final ImageView mButtonImageView;
40     private final TextView mDescriptionTextView;
41     private Animator mTextFocusGainAnimator;
42     private Animator mButtonFocusGainAnimator;
43     private Animator mTextFocusLossAnimator;
44     private Animator mButtonFocusLossAnimator;
45     private OnClickListener mOnClickListener;
46 
TvPipMenuActionButton(Context context)47     public TvPipMenuActionButton(Context context) {
48         this(context, null, 0, 0);
49     }
50 
TvPipMenuActionButton(Context context, AttributeSet attrs)51     public TvPipMenuActionButton(Context context, AttributeSet attrs) {
52         this(context, attrs, 0, 0);
53     }
54 
TvPipMenuActionButton(Context context, AttributeSet attrs, int defStyleAttr)55     public TvPipMenuActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
56         this(context, attrs, defStyleAttr, 0);
57     }
58 
TvPipMenuActionButton( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)59     public TvPipMenuActionButton(
60             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
61         super(context, attrs, defStyleAttr, defStyleRes);
62         final LayoutInflater inflater = (LayoutInflater) getContext()
63                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
64         inflater.inflate(R.layout.tv_pip_menu_action_button, this);
65 
66         mIconImageView = findViewById(R.id.icon);
67         mButtonImageView = findViewById(R.id.button);
68         mDescriptionTextView = findViewById(R.id.desc);
69 
70         final int[] values = new int[]{android.R.attr.src, android.R.attr.text};
71         final TypedArray typedArray = context.obtainStyledAttributes(attrs, values, defStyleAttr,
72                 defStyleRes);
73 
74         setImageResource(typedArray.getResourceId(0, 0));
75         final int textResId = typedArray.getResourceId(1, 0);
76         if (textResId != 0) {
77             setTextAndDescription(getContext().getString(textResId));
78         }
79 
80         typedArray.recycle();
81     }
82 
83     @Override
onFinishInflate()84     public void onFinishInflate() {
85         super.onFinishInflate();
86         mButtonImageView.setOnFocusChangeListener((v, hasFocus) -> {
87             if (hasFocus) {
88                 startFocusGainAnimation();
89             } else {
90                 startFocusLossAnimation();
91             }
92         });
93 
94         mTextFocusGainAnimator = AnimatorInflater.loadAnimator(getContext(),
95                 R.anim.tv_pip_controls_focus_gain_animation);
96         mTextFocusGainAnimator.setTarget(mDescriptionTextView);
97         mButtonFocusGainAnimator = AnimatorInflater.loadAnimator(getContext(),
98                 R.anim.tv_pip_controls_focus_gain_animation);
99         mButtonFocusGainAnimator.setTarget(mButtonImageView);
100 
101         mTextFocusLossAnimator = AnimatorInflater.loadAnimator(getContext(),
102                 R.anim.tv_pip_controls_focus_loss_animation);
103         mTextFocusLossAnimator.setTarget(mDescriptionTextView);
104         mButtonFocusLossAnimator = AnimatorInflater.loadAnimator(getContext(),
105                 R.anim.tv_pip_controls_focus_loss_animation);
106         mButtonFocusLossAnimator.setTarget(mButtonImageView);
107     }
108 
109     @Override
setOnClickListener(OnClickListener listener)110     public void setOnClickListener(OnClickListener listener) {
111         // We do not want to set an OnClickListener to the TvPipMenuActionButton itself, but only to
112         // the ImageView. So let's "cash" the listener we've been passed here and set a "proxy"
113         // listener to the ImageView.
114         mOnClickListener = listener;
115         mButtonImageView.setOnClickListener(listener != null ? this : null);
116     }
117 
118     @Override
onClick(View v)119     public void onClick(View v) {
120         if (mOnClickListener != null) {
121             // Pass the correct view - this.
122             mOnClickListener.onClick(this);
123         }
124     }
125 
126     /**
127      * Sets the drawable for the button with the given drawable.
128      */
setImageDrawable(Drawable d)129     public void setImageDrawable(Drawable d) {
130         mIconImageView.setImageDrawable(d);
131     }
132 
133     /**
134      * Sets the drawable for the button with the given resource id.
135      */
setImageResource(int resId)136     public void setImageResource(int resId) {
137         if (resId != 0) {
138             mIconImageView.setImageResource(resId);
139         }
140     }
141 
142     /**
143      * Sets the text for description the with the given string.
144      */
setTextAndDescription(CharSequence text)145     public void setTextAndDescription(CharSequence text) {
146         mButtonImageView.setContentDescription(text);
147         mDescriptionTextView.setText(text);
148     }
149 
cancelAnimator(Animator animator)150     private static void cancelAnimator(Animator animator) {
151         if (animator.isStarted()) {
152             animator.cancel();
153         }
154     }
155 
156     /**
157      * Starts the focus gain animation.
158      */
startFocusGainAnimation()159     public void startFocusGainAnimation() {
160         cancelAnimator(mButtonFocusLossAnimator);
161         cancelAnimator(mTextFocusLossAnimator);
162         mTextFocusGainAnimator.start();
163         if (mButtonImageView.getAlpha() < 1f) {
164             // If we had faded out the ripple drawable, run our manual focus change animation.
165             // See the comment at {@link #startFocusLossAnimation()} for the reason of manual
166             // animator.
167             mButtonFocusGainAnimator.start();
168         }
169     }
170 
171     /**
172      * Starts the focus loss animation.
173      */
startFocusLossAnimation()174     public void startFocusLossAnimation() {
175         cancelAnimator(mButtonFocusGainAnimator);
176         cancelAnimator(mTextFocusGainAnimator);
177         mTextFocusLossAnimator.start();
178         if (mButtonImageView.hasFocus()) {
179             // Button uses ripple that has the default animation for the focus changes.
180             // However, it doesn't expose the API to fade out while it is focused, so we should
181             // manually run the fade out animation when PIP controls row loses focus.
182             mButtonFocusLossAnimator.start();
183         }
184     }
185 
186     /**
187      * Resets to initial state.
188      */
reset()189     public void reset() {
190         cancelAnimator(mButtonFocusGainAnimator);
191         cancelAnimator(mTextFocusGainAnimator);
192         cancelAnimator(mButtonFocusLossAnimator);
193         cancelAnimator(mTextFocusLossAnimator);
194         mButtonImageView.setAlpha(1f);
195         mDescriptionTextView.setAlpha(mButtonImageView.hasFocus() ? 1f : 0f);
196     }
197 }
198