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.settings.widget;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.util.AttributeSet;
22 import android.util.Log;
23 import android.util.TypedValue;
24 import android.view.TextureView;
25 import android.view.View;
26 import android.widget.ImageView;
27 import android.widget.LinearLayout;
28 
29 import androidx.annotation.VisibleForTesting;
30 import androidx.preference.Preference;
31 import androidx.preference.PreferenceViewHolder;
32 import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
33 
34 import com.android.settings.R;
35 
36 /**
37  * A full width preference that hosts a MP4 video or a {@link AnimatedVectorDrawableCompat}.
38  */
39 public class VideoPreference extends Preference {
40 
41     private static final String TAG = "VideoPreference";
42     private final Context mContext;
43 
44     @VisibleForTesting
45     AnimationController mAnimationController;
46     @VisibleForTesting
47     boolean mAnimationAvailable;
48 
49     private float mAspectRatio = 1.0f;
50     private int mPreviewId;
51     private int mAnimationId;
52     private int mVectorAnimationId;
53     private int mHeight = LinearLayout.LayoutParams.MATCH_PARENT - 1; // video height in pixels
54     private TextureView mVideo;
55     private ImageView mPreviewImage;
56     private ImageView mPlayButton;
57 
VideoPreference(Context context)58     public VideoPreference(Context context) {
59         super(context);
60         mContext = context;
61         initialize(context, null);
62     }
63 
VideoPreference(Context context, AttributeSet attrs)64     public VideoPreference(Context context, AttributeSet attrs) {
65         super(context, attrs);
66         mContext = context;
67         initialize(context, attrs);
68     }
69 
initialize(Context context, AttributeSet attrs)70     private void initialize(Context context, AttributeSet attrs) {
71         TypedArray attributes = context.getTheme().obtainStyledAttributes(
72                 attrs,
73                 R.styleable.VideoPreference,
74                 0, 0);
75         try {
76             // if these are already set that means they were set dynamically and don't need
77             // to be loaded from xml
78             mAnimationAvailable = false;
79             mAnimationId = mAnimationId == 0
80                 ? attributes.getResourceId(R.styleable.VideoPreference_animation, 0)
81                 : mAnimationId;
82             mPreviewId = mPreviewId == 0
83                     ? attributes.getResourceId(R.styleable.VideoPreference_preview, 0)
84                     : mPreviewId;
85             mVectorAnimationId = attributes.getResourceId(
86                     R.styleable.VideoPreference_vectorAnimation, 0);
87             if (mPreviewId == 0 && mAnimationId == 0 && mVectorAnimationId == 0) {
88                 setVisible(false);
89                 return;
90             }
91             initAnimationController();
92             if (mAnimationController != null && mAnimationController.getDuration() > 0) {
93                 setVisible(true);
94                 setLayoutResource(R.layout.video_preference);
95                 mAnimationAvailable = true;
96                 updateAspectRatio();
97             } else {
98                 setVisible(false);
99             }
100         } catch (Exception e) {
101             Log.w(TAG, "Animation resource not found. Will not show animation.");
102         } finally {
103             attributes.recycle();
104         }
105     }
106 
107     @Override
onBindViewHolder(PreferenceViewHolder holder)108     public void onBindViewHolder(PreferenceViewHolder holder) {
109         super.onBindViewHolder(holder);
110 
111         if (!mAnimationAvailable) {
112             return;
113         }
114 
115         mVideo = (TextureView) holder.findViewById(R.id.video_texture_view);
116         mPreviewImage = (ImageView) holder.findViewById(R.id.video_preview_image);
117         mPlayButton = (ImageView) holder.findViewById(R.id.video_play_button);
118         final AspectRatioFrameLayout layout = (AspectRatioFrameLayout) holder.findViewById(
119                 R.id.video_container);
120 
121         mPreviewImage.setImageResource(mPreviewId);
122         layout.setAspectRatio(mAspectRatio);
123         if (mHeight >= LinearLayout.LayoutParams.MATCH_PARENT) {
124             layout.setLayoutParams(new LinearLayout.LayoutParams(
125                     LinearLayout.LayoutParams.MATCH_PARENT, mHeight));
126         }
127         if (mAnimationController != null) {
128             mAnimationController.attachView(mVideo, mPreviewImage, mPlayButton);
129         }
130     }
131 
132     @Override
onDetached()133     public void onDetached() {
134         releaseAnimationController();
135         super.onDetached();
136     }
137 
138     /**
139      * Called from {@link VideoPreferenceController} when the view is onResume
140      */
onViewVisible()141     public void onViewVisible() {
142         initAnimationController();
143     }
144 
145     /**
146      * Called from {@link VideoPreferenceController} when the view is onPause
147      */
onViewInvisible()148     public void onViewInvisible() {
149         releaseAnimationController();
150     }
151 
152     /**
153      * Sets the video for this preference. If a previous video was set this one will override it
154      * and properly release any resources and re-initialize the preference to play the new video.
155      *
156      * @param videoId   The raw res id of the video
157      * @param previewId The drawable res id of the preview image to use if the video fails to load.
158      */
setVideo(int videoId, int previewId)159     public void setVideo(int videoId, int previewId) {
160         mAnimationId = videoId;
161         mPreviewId = previewId;
162         releaseAnimationController();
163         initialize(mContext, null);
164     }
165 
initAnimationController()166     private void initAnimationController() {
167         if (mVectorAnimationId != 0) {
168             mAnimationController = new VectorAnimationController(mContext, mVectorAnimationId);
169             return;
170         }
171         if (mAnimationId != 0) {
172             mAnimationController = new MediaAnimationController(mContext, mAnimationId);
173             if (mVideo != null) {
174                 mAnimationController.attachView(mVideo, mPreviewImage, mPlayButton);
175             }
176         }
177     }
178 
releaseAnimationController()179     private void releaseAnimationController() {
180         if (mAnimationController != null) {
181             mAnimationController.release();
182             mAnimationController = null;
183         }
184     }
185 
isAnimationAvailable()186     public boolean isAnimationAvailable() {
187         return mAnimationAvailable;
188     }
189 
190     /**
191      * sets the height of the video preference
192      *
193      * @param height in dp
194      */
setHeight(float height)195     public void setHeight(float height) {
196         mHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height,
197                 mContext.getResources().getDisplayMetrics());
198     }
199 
200     @VisibleForTesting
updateAspectRatio()201     void updateAspectRatio() {
202         mAspectRatio = mAnimationController.getVideoWidth()
203                 / (float) mAnimationController.getVideoHeight();
204     }
205 
206     /**
207      * Handle animation operations.
208      */
209     interface AnimationController {
210         /**
211          * Pauses the animation.
212          */
pause()213         void pause();
214 
215         /**
216          * Starts the animation.
217          */
start()218         void start();
219 
220         /**
221          * Releases the animation object.
222          */
release()223         void release();
224 
225         /**
226          * Attaches the animation to UI view.
227          */
attachView(TextureView video, View preview, View playButton)228         void attachView(TextureView video, View preview, View playButton);
229 
230         /**
231          * Returns the animation Width.
232          */
getVideoWidth()233         int getVideoWidth();
234 
235         /**
236          * Returns the animation Height.
237          */
getVideoHeight()238         int getVideoHeight();
239 
240         /**
241          * Returns the animation duration.
242          */
getDuration()243         int getDuration();
244 
245         /**
246          * Returns if the animation is playing.
247          */
isPlaying()248         boolean isPlaying();
249     }
250 }
251