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