Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 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 | |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 19 | import android.content.Context; |
| 20 | import android.content.res.TypedArray; |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 21 | import android.util.AttributeSet; |
| 22 | import android.util.Log; |
Beverly | 2f41418 | 2019-05-22 10:46:34 -0400 | [diff] [blame] | 23 | import android.util.TypedValue; |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 24 | import android.view.TextureView; |
| 25 | import android.view.View; |
| 26 | import android.widget.ImageView; |
Beverly | 2f41418 | 2019-05-22 10:46:34 -0400 | [diff] [blame] | 27 | import android.widget.LinearLayout; |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 28 | |
Fan Zhang | c7162cd | 2018-06-18 15:21:41 -0700 | [diff] [blame] | 29 | import androidx.annotation.VisibleForTesting; |
| 30 | import androidx.preference.Preference; |
| 31 | import androidx.preference.PreferenceViewHolder; |
Raff Tsai | 953da2e | 2019-10-31 14:03:34 +0800 | [diff] [blame] | 32 | import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat; |
Fan Zhang | c7162cd | 2018-06-18 15:21:41 -0700 | [diff] [blame] | 33 | |
Fan Zhang | 23f8d59 | 2018-08-28 15:11:40 -0700 | [diff] [blame] | 34 | import com.android.settings.R; |
| 35 | |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 36 | /** |
Raff Tsai | 953da2e | 2019-10-31 14:03:34 +0800 | [diff] [blame] | 37 | * A full width preference that hosts a MP4 video or a {@link AnimatedVectorDrawableCompat}. |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 38 | */ |
| 39 | public class VideoPreference extends Preference { |
| 40 | |
| 41 | private static final String TAG = "VideoPreference"; |
| 42 | private final Context mContext; |
| 43 | |
Lei Yu | 0a358ba | 2018-03-30 09:50:02 -0700 | [diff] [blame] | 44 | @VisibleForTesting |
Raff Tsai | 953da2e | 2019-10-31 14:03:34 +0800 | [diff] [blame] | 45 | AnimationController mAnimationController; |
Lei Yu | 0a358ba | 2018-03-30 09:50:02 -0700 | [diff] [blame] | 46 | @VisibleForTesting |
| 47 | boolean mAnimationAvailable; |
hughchen | 507218b | 2020-02-27 14:16:11 +0800 | [diff] [blame] | 48 | |
Beverly | 2f41418 | 2019-05-22 10:46:34 -0400 | [diff] [blame] | 49 | private float mAspectRatio = 1.0f; |
Raff Tsai | 953da2e | 2019-10-31 14:03:34 +0800 | [diff] [blame] | 50 | private int mPreviewId; |
Salvador Martinez | 1053ec0 | 2019-03-20 10:52:52 -0700 | [diff] [blame] | 51 | private int mAnimationId; |
Raff Tsai | 953da2e | 2019-10-31 14:03:34 +0800 | [diff] [blame] | 52 | private int mVectorAnimationId; |
Beverly | 2f41418 | 2019-05-22 10:46:34 -0400 | [diff] [blame] | 53 | private int mHeight = LinearLayout.LayoutParams.MATCH_PARENT - 1; // video height in pixels |
Raff Tsai | 9547fa7 | 2019-11-05 13:24:08 +0800 | [diff] [blame] | 54 | private TextureView mVideo; |
| 55 | private ImageView mPreviewImage; |
| 56 | private ImageView mPlayButton; |
Salvador Martinez | 1053ec0 | 2019-03-20 10:52:52 -0700 | [diff] [blame] | 57 | |
| 58 | public VideoPreference(Context context) { |
| 59 | super(context); |
| 60 | mContext = context; |
| 61 | initialize(context, null); |
| 62 | } |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 63 | |
| 64 | public VideoPreference(Context context, AttributeSet attrs) { |
| 65 | super(context, attrs); |
| 66 | mContext = context; |
Salvador Martinez | 1053ec0 | 2019-03-20 10:52:52 -0700 | [diff] [blame] | 67 | initialize(context, attrs); |
| 68 | } |
| 69 | |
| 70 | private void initialize(Context context, AttributeSet attrs) { |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 71 | TypedArray attributes = context.getTheme().obtainStyledAttributes( |
| 72 | attrs, |
Salvador Martinez | 1053ec0 | 2019-03-20 10:52:52 -0700 | [diff] [blame] | 73 | R.styleable.VideoPreference, |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 74 | 0, 0); |
| 75 | try { |
Salvador Martinez | 1053ec0 | 2019-03-20 10:52:52 -0700 | [diff] [blame] | 76 | // if these are already set that means they were set dynamically and don't need |
| 77 | // to be loaded from xml |
LuK1337 | 9c3ab97 | 2019-10-09 16:08:12 +0200 | [diff] [blame] | 78 | mAnimationAvailable = false; |
Salvador Martinez | 1053ec0 | 2019-03-20 10:52:52 -0700 | [diff] [blame] | 79 | mAnimationId = mAnimationId == 0 |
| 80 | ? attributes.getResourceId(R.styleable.VideoPreference_animation, 0) |
| 81 | : mAnimationId; |
Raff Tsai | 953da2e | 2019-10-31 14:03:34 +0800 | [diff] [blame] | 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) { |
LuK1337 | 9c3ab97 | 2019-10-09 16:08:12 +0200 | [diff] [blame] | 88 | setVisible(false); |
Salvador Martinez | 1053ec0 | 2019-03-20 10:52:52 -0700 | [diff] [blame] | 89 | return; |
| 90 | } |
Raff Tsai | 953da2e | 2019-10-31 14:03:34 +0800 | [diff] [blame] | 91 | initAnimationController(); |
| 92 | if (mAnimationController != null && mAnimationController.getDuration() > 0) { |
Fan Zhang | 26ce68c | 2017-03-07 15:58:35 -0800 | [diff] [blame] | 93 | setVisible(true); |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 94 | setLayoutResource(R.layout.video_preference); |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 95 | mAnimationAvailable = true; |
Lei Yu | 0a358ba | 2018-03-30 09:50:02 -0700 | [diff] [blame] | 96 | updateAspectRatio(); |
Fan Zhang | 26ce68c | 2017-03-07 15:58:35 -0800 | [diff] [blame] | 97 | } else { |
| 98 | setVisible(false); |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 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 |
| 108 | public void onBindViewHolder(PreferenceViewHolder holder) { |
| 109 | super.onBindViewHolder(holder); |
| 110 | |
| 111 | if (!mAnimationAvailable) { |
| 112 | return; |
| 113 | } |
| 114 | |
Raff Tsai | 9547fa7 | 2019-11-05 13:24:08 +0800 | [diff] [blame] | 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); |
Lei Yu | 0a358ba | 2018-03-30 09:50:02 -0700 | [diff] [blame] | 118 | final AspectRatioFrameLayout layout = (AspectRatioFrameLayout) holder.findViewById( |
| 119 | R.id.video_container); |
| 120 | |
Raff Tsai | 9547fa7 | 2019-11-05 13:24:08 +0800 | [diff] [blame] | 121 | mPreviewImage.setImageResource(mPreviewId); |
Beverly | 2f41418 | 2019-05-22 10:46:34 -0400 | [diff] [blame] | 122 | layout.setAspectRatio(mAspectRatio); |
| 123 | if (mHeight >= LinearLayout.LayoutParams.MATCH_PARENT) { |
| 124 | layout.setLayoutParams(new LinearLayout.LayoutParams( |
| 125 | LinearLayout.LayoutParams.MATCH_PARENT, mHeight)); |
| 126 | } |
Sunny Shao | b808eb0 | 2019-11-08 10:53:53 +0800 | [diff] [blame] | 127 | if (mAnimationController != null) { |
| 128 | mAnimationController.attachView(mVideo, mPreviewImage, mPlayButton); |
| 129 | } |
Salvador Martinez | 1053ec0 | 2019-03-20 10:52:52 -0700 | [diff] [blame] | 130 | } |
| 131 | |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 132 | @Override |
| 133 | public void onDetached() { |
Raff Tsai | 953da2e | 2019-10-31 14:03:34 +0800 | [diff] [blame] | 134 | releaseAnimationController(); |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 135 | super.onDetached(); |
| 136 | } |
| 137 | |
Raff Tsai | 6892763 | 2019-11-01 10:37:27 +0800 | [diff] [blame] | 138 | /** |
| 139 | * Called from {@link VideoPreferenceController} when the view is onResume |
| 140 | */ |
| 141 | public void onViewVisible() { |
Raff Tsai | 953da2e | 2019-10-31 14:03:34 +0800 | [diff] [blame] | 142 | initAnimationController(); |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 143 | } |
| 144 | |
Raff Tsai | 6892763 | 2019-11-01 10:37:27 +0800 | [diff] [blame] | 145 | /** |
| 146 | * Called from {@link VideoPreferenceController} when the view is onPause |
| 147 | */ |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 148 | public void onViewInvisible() { |
Raff Tsai | 953da2e | 2019-10-31 14:03:34 +0800 | [diff] [blame] | 149 | releaseAnimationController(); |
Raff Tsai | 2831b14 | 2019-01-02 17:07:22 +0800 | [diff] [blame] | 150 | } |
| 151 | |
Salvador Martinez | 1053ec0 | 2019-03-20 10:52:52 -0700 | [diff] [blame] | 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 | * |
Raff Tsai | 953da2e | 2019-10-31 14:03:34 +0800 | [diff] [blame] | 156 | * @param videoId The raw res id of the video |
Salvador Martinez | 1053ec0 | 2019-03-20 10:52:52 -0700 | [diff] [blame] | 157 | * @param previewId The drawable res id of the preview image to use if the video fails to load. |
| 158 | */ |
| 159 | public void setVideo(int videoId, int previewId) { |
| 160 | mAnimationId = videoId; |
Raff Tsai | 953da2e | 2019-10-31 14:03:34 +0800 | [diff] [blame] | 161 | mPreviewId = previewId; |
| 162 | releaseAnimationController(); |
Salvador Martinez | 1053ec0 | 2019-03-20 10:52:52 -0700 | [diff] [blame] | 163 | initialize(mContext, null); |
| 164 | } |
| 165 | |
Raff Tsai | 953da2e | 2019-10-31 14:03:34 +0800 | [diff] [blame] | 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); |
Raff Tsai | 9547fa7 | 2019-11-05 13:24:08 +0800 | [diff] [blame] | 173 | if (mVideo != null) { |
| 174 | mAnimationController.attachView(mVideo, mPreviewImage, mPlayButton); |
| 175 | } |
Raff Tsai | 2831b14 | 2019-01-02 17:07:22 +0800 | [diff] [blame] | 176 | } |
| 177 | } |
| 178 | |
Raff Tsai | 953da2e | 2019-10-31 14:03:34 +0800 | [diff] [blame] | 179 | private void releaseAnimationController() { |
| 180 | if (mAnimationController != null) { |
| 181 | mAnimationController.release(); |
| 182 | mAnimationController = null; |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 183 | } |
| 184 | } |
Doris Ling | a689787 | 2017-07-11 10:38:24 -0700 | [diff] [blame] | 185 | |
LuK1337 | 9c3ab97 | 2019-10-09 16:08:12 +0200 | [diff] [blame] | 186 | public boolean isAnimationAvailable() { |
| 187 | return mAnimationAvailable; |
| 188 | } |
| 189 | |
Beverly | 2f41418 | 2019-05-22 10:46:34 -0400 | [diff] [blame] | 190 | /** |
| 191 | * sets the height of the video preference |
Raff Tsai | 953da2e | 2019-10-31 14:03:34 +0800 | [diff] [blame] | 192 | * |
Beverly | 2f41418 | 2019-05-22 10:46:34 -0400 | [diff] [blame] | 193 | * @param height in dp |
| 194 | */ |
| 195 | public void setHeight(float height) { |
| 196 | mHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, |
| 197 | mContext.getResources().getDisplayMetrics()); |
| 198 | } |
| 199 | |
Lei Yu | 0a358ba | 2018-03-30 09:50:02 -0700 | [diff] [blame] | 200 | @VisibleForTesting |
| 201 | void updateAspectRatio() { |
Raff Tsai | 953da2e | 2019-10-31 14:03:34 +0800 | [diff] [blame] | 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 | */ |
| 213 | void pause(); |
| 214 | |
| 215 | /** |
| 216 | * Starts the animation. |
| 217 | */ |
| 218 | void start(); |
| 219 | |
| 220 | /** |
| 221 | * Releases the animation object. |
| 222 | */ |
| 223 | void release(); |
| 224 | |
| 225 | /** |
| 226 | * Attaches the animation to UI view. |
| 227 | */ |
| 228 | void attachView(TextureView video, View preview, View playButton); |
| 229 | |
| 230 | /** |
| 231 | * Returns the animation Width. |
| 232 | */ |
| 233 | int getVideoWidth(); |
| 234 | |
| 235 | /** |
| 236 | * Returns the animation Height. |
| 237 | */ |
| 238 | int getVideoHeight(); |
| 239 | |
| 240 | /** |
| 241 | * Returns the animation duration. |
| 242 | */ |
| 243 | int getDuration(); |
| 244 | |
| 245 | /** |
| 246 | * Returns if the animation is playing. |
| 247 | */ |
| 248 | boolean isPlaying(); |
Lei Yu | 0a358ba | 2018-03-30 09:50:02 -0700 | [diff] [blame] | 249 | } |
Fan Zhang | 33b0d91 | 2016-11-09 11:35:10 -0800 | [diff] [blame] | 250 | } |