1 /*
2  * Copyright (C) 2016 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.systemui.tv.pip;
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.util.AttributeSet;
24 import android.view.LayoutInflater;
25 import android.view.View.OnFocusChangeListener;
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.systemui.R;
32 
33 /**
34  * A view containing PIP controls including fullscreen, close, and media controls.
35  */
36 public class PipControlButtonView extends RelativeLayout {
37     private OnFocusChangeListener mFocusChangeListener;
38     private ImageView mIconImageView;
39     ImageView mButtonImageView;
40     private TextView mDescriptionTextView;
41     private Animator mTextFocusGainAnimator;
42     private Animator mButtonFocusGainAnimator;
43     private Animator mTextFocusLossAnimator;
44     private Animator mButtonFocusLossAnimator;
45 
46     private final OnFocusChangeListener mInternalFocusChangeListener =
47             new OnFocusChangeListener() {
48                 @Override
49                 public void onFocusChange(View v, boolean hasFocus) {
50                     if (hasFocus) {
51                         startFocusGainAnimation();
52                     } else {
53                         startFocusLossAnimation();
54                     }
55 
56                     if (mFocusChangeListener != null) {
57                         mFocusChangeListener.onFocusChange(PipControlButtonView.this, hasFocus);
58                     }
59                 }
60             };
61 
PipControlButtonView(Context context)62     public PipControlButtonView(Context context) {
63         this(context, null, 0, 0);
64     }
65 
PipControlButtonView(Context context, AttributeSet attrs)66     public PipControlButtonView(Context context, AttributeSet attrs) {
67         this(context, attrs, 0, 0);
68     }
69 
PipControlButtonView(Context context, AttributeSet attrs, int defStyleAttr)70     public PipControlButtonView(Context context, AttributeSet attrs, int defStyleAttr) {
71         this(context, attrs, defStyleAttr, 0);
72     }
73 
PipControlButtonView( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)74     public PipControlButtonView(
75             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
76         super(context, attrs, defStyleAttr, defStyleRes);
77         LayoutInflater inflater = (LayoutInflater) getContext()
78                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
79         inflater.inflate(R.layout.tv_pip_control_button, this);
80 
81         mIconImageView = (ImageView) findViewById(R.id.icon);
82         mButtonImageView = (ImageView) findViewById(R.id.button);
83         mDescriptionTextView = (TextView) findViewById(R.id.desc);
84 
85         int[] values = new int[] {android.R.attr.src, android.R.attr.text};
86         TypedArray typedArray =
87             context.obtainStyledAttributes(attrs, values, defStyleAttr, defStyleRes);
88 
89         setImageResource(typedArray.getResourceId(0, 0));
90         setText(typedArray.getResourceId(1, 0));
91 
92         typedArray.recycle();
93     }
94 
95     @Override
onFinishInflate()96     public void onFinishInflate() {
97         super.onFinishInflate();
98         mButtonImageView.setOnFocusChangeListener(mInternalFocusChangeListener);
99 
100         mTextFocusGainAnimator = AnimatorInflater.loadAnimator(getContext(),
101                 R.anim.tv_pip_controls_focus_gain_animation);
102         mTextFocusGainAnimator.setTarget(mDescriptionTextView);
103         mButtonFocusGainAnimator = AnimatorInflater.loadAnimator(getContext(),
104                 R.anim.tv_pip_controls_focus_gain_animation);
105         mButtonFocusGainAnimator.setTarget(mButtonImageView);
106 
107         mTextFocusLossAnimator = AnimatorInflater.loadAnimator(getContext(),
108                 R.anim.tv_pip_controls_focus_loss_animation);
109         mTextFocusLossAnimator.setTarget(mDescriptionTextView);
110         mButtonFocusLossAnimator = AnimatorInflater.loadAnimator(getContext(),
111                 R.anim.tv_pip_controls_focus_loss_animation);
112         mButtonFocusLossAnimator.setTarget(mButtonImageView);
113     }
114 
115     @Override
setOnClickListener(OnClickListener listener)116     public void setOnClickListener(OnClickListener listener) {
117         mButtonImageView.setOnClickListener(listener);
118     }
119 
120     @Override
setOnFocusChangeListener(OnFocusChangeListener listener)121     public void setOnFocusChangeListener(OnFocusChangeListener listener) {
122         mFocusChangeListener = listener;
123     }
124 
125     /**
126      * Sets the drawable for the button with the given resource id.
127      */
setImageResource(int resId)128     public void setImageResource(int resId) {
129         mIconImageView.setImageResource(resId);
130     }
131 
132     /**
133      * Sets the text for description the with the given resource id.
134      */
setText(int resId)135     public void setText(int resId) {
136         mButtonImageView.setContentDescription(getContext().getString(resId));
137         mDescriptionTextView.setText(resId);
138     }
139 
cancelAnimator(Animator animator)140     private static void cancelAnimator(Animator animator) {
141         if (animator.isStarted()) {
142             animator.cancel();
143         }
144     }
145 
146     /**
147      * Starts the focus gain animation.
148      */
startFocusGainAnimation()149     public void startFocusGainAnimation() {
150         cancelAnimator(mButtonFocusLossAnimator);
151         cancelAnimator(mTextFocusLossAnimator);
152         mTextFocusGainAnimator.start();
153         if (mButtonImageView.getAlpha() < 1f) {
154             // If we had faded out the ripple drawable, run our manual focus change animation.
155             // See the comment at {@link #startFocusLossAnimation()} for the reason of manual
156             // animator.
157             mButtonFocusGainAnimator.start();
158         }
159     }
160 
161     /**
162      * Starts the focus loss animation.
163      */
startFocusLossAnimation()164     public void startFocusLossAnimation() {
165         cancelAnimator(mButtonFocusGainAnimator);
166         cancelAnimator(mTextFocusGainAnimator);
167         mTextFocusLossAnimator.start();
168         if (mButtonImageView.hasFocus()) {
169             // Button uses ripple that has the default animation for the focus changes.
170             // Howevever, it doesn't expose the API to fade out while it is focused,
171             // so we should manually run the fade out animation when PIP controls row loses focus.
172             mButtonFocusLossAnimator.start();
173         }
174     }
175 
176     /**
177      * Resets to initial state.
178      */
reset()179     public void reset() {
180         cancelAnimator(mButtonFocusGainAnimator);
181         cancelAnimator(mTextFocusGainAnimator);
182         cancelAnimator(mButtonFocusLossAnimator);
183         cancelAnimator(mTextFocusLossAnimator);
184         mButtonImageView.setAlpha(1f);
185         mDescriptionTextView.setAlpha(mButtonImageView.hasFocus() ? 1f : 0f);
186     }
187 }
188