Loading res/values/attrs.xml +1 −0 Original line number Diff line number Diff line Loading @@ -119,6 +119,7 @@ <declare-styleable name="VideoPreference"> <attr name="animation" format="reference" /> <attr name="preview" format="reference" /> <attr name="vectorAnimation" format="reference" /> </declare-styleable> <!-- For AspectRatioFrameLayout --> Loading src/com/android/settings/widget/MediaAnimationController.java 0 → 100644 +149 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.widget; import android.content.ContentResolver; import android.content.Context; import android.graphics.SurfaceTexture; import android.media.MediaPlayer; import android.net.Uri; import android.view.Surface; import android.view.TextureView; import android.view.View; /** * A {@link VideoPreference.AnimationController} containing a {@link * MediaPlayer}. The controller is used by {@link VideoPreference} to display * a mp4 resource. */ class MediaAnimationController implements VideoPreference.AnimationController { private MediaPlayer mMediaPlayer; private boolean mVideoReady; MediaAnimationController(Context context, int videoId) { final Uri videoPath = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) .authority(context.getPackageName()) .appendPath(String.valueOf(videoId)) .build(); mMediaPlayer = MediaPlayer.create(context, videoPath); // when the playback res is invalid or others, MediaPlayer create may fail // and return null, so need add the null judgement. if (mMediaPlayer != null) { mMediaPlayer.seekTo(0); mMediaPlayer.setOnSeekCompleteListener(mp -> mVideoReady = true); mMediaPlayer.setOnPreparedListener(mediaPlayer -> mediaPlayer.setLooping(true)); } } @Override public int getVideoWidth() { return mMediaPlayer.getVideoWidth(); } @Override public int getVideoHeight() { return mMediaPlayer.getVideoHeight(); } @Override public void pause() { mMediaPlayer.pause(); } @Override public void start() { mMediaPlayer.start(); } @Override public boolean isPlaying() { return mMediaPlayer.isPlaying(); } @Override public int getDuration() { return mMediaPlayer.getDuration(); } @Override public void attachView(TextureView video, View preview, View playButton) { updateViewStates(preview, playButton); video.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { if (mMediaPlayer != null) { final Surface surface = new Surface(surfaceTexture); mMediaPlayer.setSurface(surface); } } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { preview.setVisibility(View.VISIBLE); return false; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { if (mVideoReady) { if (preview.getVisibility() == View.VISIBLE) { preview.setVisibility(View.GONE); } if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) { mMediaPlayer.start(); playButton.setVisibility(View.GONE); } } if (mMediaPlayer != null && !mMediaPlayer.isPlaying() && playButton.getVisibility() != View.VISIBLE) { playButton.setVisibility(View.VISIBLE); } } }); video.setOnClickListener(v -> updateViewStates(preview, playButton)); } @Override public void release() { if (mMediaPlayer != null) { mMediaPlayer.stop(); mMediaPlayer.reset(); mMediaPlayer.release(); mMediaPlayer = null; mVideoReady = false; } } private void updateViewStates(View imageView, View playButton) { if (mMediaPlayer.isPlaying()) { mMediaPlayer.pause(); playButton.setVisibility(View.VISIBLE); imageView.setVisibility(View.VISIBLE); } else { imageView.setVisibility(View.GONE); playButton.setVisibility(View.GONE); mMediaPlayer.start(); } } } src/com/android/settings/widget/VectorAnimationController.java 0 → 100644 +111 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.widget; import android.content.Context; import android.graphics.drawable.Drawable; import android.view.TextureView; import android.view.View; import androidx.vectordrawable.graphics.drawable.Animatable2Compat; import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat; /** * A {@link VideoPreference.AnimationController} containing a {@link * AnimatedVectorDrawableCompat}. The controller is used by {@link VideoPreference} * to display AnimatedVectorDrawable content. */ class VectorAnimationController implements VideoPreference.AnimationController { private AnimatedVectorDrawableCompat mAnimatedVectorDrawableCompat; private Drawable mPreviewDrawable; private Animatable2Compat.AnimationCallback mAnimationCallback; /** * Called by a preference panel fragment to finish itself. * * @param context Application Context * @param animationId An {@link android.graphics.drawable.AnimationDrawable} resource id */ VectorAnimationController(Context context, int animationId) { mAnimatedVectorDrawableCompat = AnimatedVectorDrawableCompat.create(context, animationId); mAnimationCallback = new Animatable2Compat.AnimationCallback() { @Override public void onAnimationEnd(Drawable drawable) { mAnimatedVectorDrawableCompat.start(); } }; } @Override public int getVideoWidth() { return mAnimatedVectorDrawableCompat.getIntrinsicWidth(); } @Override public int getVideoHeight() { return mAnimatedVectorDrawableCompat.getIntrinsicHeight(); } @Override public void pause() { mAnimatedVectorDrawableCompat.stop(); } @Override public void start() { mAnimatedVectorDrawableCompat.start(); } @Override public boolean isPlaying() { return mAnimatedVectorDrawableCompat.isRunning(); } @Override public int getDuration() { // We can't get duration from AnimatedVectorDrawable, just return a non zero value. return 5000; } @Override public void attachView(TextureView video, View preview, View playButton) { mPreviewDrawable = preview.getForeground(); video.setVisibility(View.GONE); updateViewStates(preview, playButton); preview.setOnClickListener(v -> updateViewStates(preview, playButton)); } @Override public void release() { mAnimatedVectorDrawableCompat.stop(); mAnimatedVectorDrawableCompat.clearAnimationCallbacks(); } private void updateViewStates(View imageView, View playButton) { if (mAnimatedVectorDrawableCompat.isRunning()) { mAnimatedVectorDrawableCompat.stop(); mAnimatedVectorDrawableCompat.clearAnimationCallbacks(); playButton.setVisibility(View.VISIBLE); imageView.setForeground(mPreviewDrawable); } else { playButton.setVisibility(View.GONE); imageView.setForeground(mAnimatedVectorDrawableCompat); mAnimatedVectorDrawableCompat.start(); mAnimatedVectorDrawableCompat.registerAnimationCallback(mAnimationCallback); } } } src/com/android/settings/widget/VideoPreference.java +81 −116 Original line number Diff line number Diff line Loading @@ -16,16 +16,11 @@ package com.android.settings.widget; import android.content.ContentResolver; import android.content.Context; import android.content.res.TypedArray; import android.graphics.SurfaceTexture; import android.media.MediaPlayer; import android.net.Uri; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.ImageView; Loading @@ -34,30 +29,27 @@ import android.widget.LinearLayout; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat; import com.android.settings.R; /** * A full width preference that hosts a MP4 video. * A full width preference that hosts a MP4 video or a {@link AnimatedVectorDrawableCompat}. */ public class VideoPreference extends Preference { private static final String TAG = "VideoPreference"; private final Context mContext; private Uri mVideoPath; @VisibleForTesting MediaPlayer mMediaPlayer; AnimationController mAnimationController; @VisibleForTesting boolean mAnimationAvailable; @VisibleForTesting boolean mVideoReady; private boolean mVideoPaused; private float mAspectRatio = 1.0f; private int mPreviewResource; private boolean mViewVisible; private Surface mSurface; private int mPreviewId; private int mAnimationId; private int mVectorAnimationId; private int mHeight = LinearLayout.LayoutParams.MATCH_PARENT - 1; // video height in pixels public VideoPreference(Context context) { Loading @@ -84,19 +76,17 @@ public class VideoPreference extends Preference { mAnimationId = mAnimationId == 0 ? attributes.getResourceId(R.styleable.VideoPreference_animation, 0) : mAnimationId; mVideoPath = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) .authority(context.getPackageName()) .appendPath(String.valueOf(mAnimationId)) .build(); mPreviewResource = mPreviewResource == 0 mPreviewId = mPreviewId == 0 ? attributes.getResourceId(R.styleable.VideoPreference_preview, 0) : mPreviewResource; if (mPreviewResource == 0 && mAnimationId == 0) { : mPreviewId; mVectorAnimationId = attributes.getResourceId( R.styleable.VideoPreference_vectorAnimation, 0); if (mPreviewId == 0 && mAnimationId == 0 && mVectorAnimationId == 0) { setVisible(false); return; } initMediaPlayer(); if (mMediaPlayer != null && mMediaPlayer.getDuration() > 0) { initAnimationController(); if (mAnimationController != null && mAnimationController.getDuration() > 0) { setVisible(true); setLayoutResource(R.layout.video_preference); mAnimationAvailable = true; Loading @@ -120,96 +110,33 @@ public class VideoPreference extends Preference { } final TextureView video = (TextureView) holder.findViewById(R.id.video_texture_view); final ImageView imageView = (ImageView) holder.findViewById(R.id.video_preview_image); final ImageView previewImage = (ImageView) holder.findViewById(R.id.video_preview_image); final ImageView playButton = (ImageView) holder.findViewById(R.id.video_play_button); final AspectRatioFrameLayout layout = (AspectRatioFrameLayout) holder.findViewById( R.id.video_container); imageView.setImageResource(mPreviewResource); previewImage.setImageResource(mPreviewId); layout.setAspectRatio(mAspectRatio); if (mHeight >= LinearLayout.LayoutParams.MATCH_PARENT) { layout.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, mHeight)); } updateViewStates(imageView, playButton); video.setOnClickListener(v -> updateViewStates(imageView, playButton)); video.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { if (mMediaPlayer != null) { mSurface = new Surface(surfaceTexture); mMediaPlayer.setSurface(mSurface); } } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { imageView.setVisibility(View.VISIBLE); return false; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { if (!mViewVisible) { return; } if (mVideoReady) { if (imageView.getVisibility() == View.VISIBLE) { imageView.setVisibility(View.GONE); } if (!mVideoPaused && mMediaPlayer != null && !mMediaPlayer.isPlaying()) { mMediaPlayer.start(); playButton.setVisibility(View.GONE); } } if (mMediaPlayer != null && !mMediaPlayer.isPlaying() && playButton.getVisibility() != View.VISIBLE) { playButton.setVisibility(View.VISIBLE); } } }); } @VisibleForTesting void updateViewStates(ImageView imageView, ImageView playButton) { if (mMediaPlayer != null) { if (mMediaPlayer.isPlaying()) { mMediaPlayer.pause(); playButton.setVisibility(View.VISIBLE); imageView.setVisibility(View.VISIBLE); mVideoPaused = true; } else { imageView.setVisibility(View.GONE); playButton.setVisibility(View.GONE); mMediaPlayer.start(); mVideoPaused = false; } } mAnimationController.attachView(video, previewImage, playButton); } @Override public void onDetached() { releaseMediaPlayer(); releaseAnimationController(); super.onDetached(); } public void onViewVisible(boolean videoPaused) { mViewVisible = true; mVideoPaused = videoPaused; initMediaPlayer(); initAnimationController(); } public void onViewInvisible() { mViewVisible = false; releaseMediaPlayer(); releaseAnimationController(); } /** Loading @@ -221,34 +148,25 @@ public class VideoPreference extends Preference { */ public void setVideo(int videoId, int previewId) { mAnimationId = videoId; mPreviewResource = previewId; releaseMediaPlayer(); mPreviewId = previewId; releaseAnimationController(); initialize(mContext, null); } private void initMediaPlayer() { if (mMediaPlayer == null) { mMediaPlayer = MediaPlayer.create(mContext, mVideoPath); // when the playback res is invalid or others, MediaPlayer create may fail // and return null, so need add the null judgement. if (mMediaPlayer != null) { mMediaPlayer.seekTo(0); mMediaPlayer.setOnSeekCompleteListener(mp -> mVideoReady = true); mMediaPlayer.setOnPreparedListener(mediaPlayer -> mediaPlayer.setLooping(true)); if (mSurface != null) { mMediaPlayer.setSurface(mSurface); } private void initAnimationController() { if (mVectorAnimationId != 0) { mAnimationController = new VectorAnimationController(mContext, mVectorAnimationId); return; } if (mAnimationId != 0) { mAnimationController = new MediaAnimationController(mContext, mAnimationId); } } private void releaseMediaPlayer() { if (mMediaPlayer != null) { mMediaPlayer.stop(); mMediaPlayer.reset(); mMediaPlayer.release(); mMediaPlayer = null; mVideoReady = false; private void releaseAnimationController() { if (mAnimationController != null) { mAnimationController.release(); mAnimationController = null; } } Loading @@ -262,6 +180,7 @@ public class VideoPreference extends Preference { /** * sets the height of the video preference * * @param height in dp */ public void setHeight(float height) { Loading @@ -271,6 +190,52 @@ public class VideoPreference extends Preference { @VisibleForTesting void updateAspectRatio() { mAspectRatio = mMediaPlayer.getVideoWidth() / (float) mMediaPlayer.getVideoHeight(); mAspectRatio = mAnimationController.getVideoWidth() / (float) mAnimationController.getVideoHeight(); } /** * Handle animation operations. */ interface AnimationController { /** * Pauses the animation. */ void pause(); /** * Starts the animation. */ void start(); /** * Releases the animation object. */ void release(); /** * Attaches the animation to UI view. */ void attachView(TextureView video, View preview, View playButton); /** * Returns the animation Width. */ int getVideoWidth(); /** * Returns the animation Height. */ int getVideoHeight(); /** * Returns the animation duration. */ int getDuration(); /** * Returns if the animation is playing. */ boolean isPlaying(); } } tests/robotests/src/com/android/settings/testutils/shadow/ShadowSettingsMediaPlayer.java 0 → 100644 +51 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.testutils.shadow; import static org.robolectric.shadows.ShadowMediaPlayer.State.INITIALIZED; import android.content.Context; import android.media.MediaPlayer; import android.net.Uri; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowMediaPlayer; import org.robolectric.shadows.util.DataSource; @Implements(MediaPlayer.class) public class ShadowSettingsMediaPlayer extends ShadowMediaPlayer { @Implementation public static MediaPlayer create(Context context, Uri uri) { final DataSource ds = DataSource.toDataSource(context, uri); addMediaInfo(ds, new ShadowMediaPlayer.MediaInfo()); final MediaPlayer mp = new MediaPlayer(); final ShadowMediaPlayer shadow = Shadow.extract(mp); try { shadow.setDataSource(ds); shadow.setState(INITIALIZED); mp.prepare(); } catch (Exception e) { return null; } return mp; } } Loading
res/values/attrs.xml +1 −0 Original line number Diff line number Diff line Loading @@ -119,6 +119,7 @@ <declare-styleable name="VideoPreference"> <attr name="animation" format="reference" /> <attr name="preview" format="reference" /> <attr name="vectorAnimation" format="reference" /> </declare-styleable> <!-- For AspectRatioFrameLayout --> Loading
src/com/android/settings/widget/MediaAnimationController.java 0 → 100644 +149 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.widget; import android.content.ContentResolver; import android.content.Context; import android.graphics.SurfaceTexture; import android.media.MediaPlayer; import android.net.Uri; import android.view.Surface; import android.view.TextureView; import android.view.View; /** * A {@link VideoPreference.AnimationController} containing a {@link * MediaPlayer}. The controller is used by {@link VideoPreference} to display * a mp4 resource. */ class MediaAnimationController implements VideoPreference.AnimationController { private MediaPlayer mMediaPlayer; private boolean mVideoReady; MediaAnimationController(Context context, int videoId) { final Uri videoPath = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) .authority(context.getPackageName()) .appendPath(String.valueOf(videoId)) .build(); mMediaPlayer = MediaPlayer.create(context, videoPath); // when the playback res is invalid or others, MediaPlayer create may fail // and return null, so need add the null judgement. if (mMediaPlayer != null) { mMediaPlayer.seekTo(0); mMediaPlayer.setOnSeekCompleteListener(mp -> mVideoReady = true); mMediaPlayer.setOnPreparedListener(mediaPlayer -> mediaPlayer.setLooping(true)); } } @Override public int getVideoWidth() { return mMediaPlayer.getVideoWidth(); } @Override public int getVideoHeight() { return mMediaPlayer.getVideoHeight(); } @Override public void pause() { mMediaPlayer.pause(); } @Override public void start() { mMediaPlayer.start(); } @Override public boolean isPlaying() { return mMediaPlayer.isPlaying(); } @Override public int getDuration() { return mMediaPlayer.getDuration(); } @Override public void attachView(TextureView video, View preview, View playButton) { updateViewStates(preview, playButton); video.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { if (mMediaPlayer != null) { final Surface surface = new Surface(surfaceTexture); mMediaPlayer.setSurface(surface); } } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { preview.setVisibility(View.VISIBLE); return false; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { if (mVideoReady) { if (preview.getVisibility() == View.VISIBLE) { preview.setVisibility(View.GONE); } if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) { mMediaPlayer.start(); playButton.setVisibility(View.GONE); } } if (mMediaPlayer != null && !mMediaPlayer.isPlaying() && playButton.getVisibility() != View.VISIBLE) { playButton.setVisibility(View.VISIBLE); } } }); video.setOnClickListener(v -> updateViewStates(preview, playButton)); } @Override public void release() { if (mMediaPlayer != null) { mMediaPlayer.stop(); mMediaPlayer.reset(); mMediaPlayer.release(); mMediaPlayer = null; mVideoReady = false; } } private void updateViewStates(View imageView, View playButton) { if (mMediaPlayer.isPlaying()) { mMediaPlayer.pause(); playButton.setVisibility(View.VISIBLE); imageView.setVisibility(View.VISIBLE); } else { imageView.setVisibility(View.GONE); playButton.setVisibility(View.GONE); mMediaPlayer.start(); } } }
src/com/android/settings/widget/VectorAnimationController.java 0 → 100644 +111 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.widget; import android.content.Context; import android.graphics.drawable.Drawable; import android.view.TextureView; import android.view.View; import androidx.vectordrawable.graphics.drawable.Animatable2Compat; import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat; /** * A {@link VideoPreference.AnimationController} containing a {@link * AnimatedVectorDrawableCompat}. The controller is used by {@link VideoPreference} * to display AnimatedVectorDrawable content. */ class VectorAnimationController implements VideoPreference.AnimationController { private AnimatedVectorDrawableCompat mAnimatedVectorDrawableCompat; private Drawable mPreviewDrawable; private Animatable2Compat.AnimationCallback mAnimationCallback; /** * Called by a preference panel fragment to finish itself. * * @param context Application Context * @param animationId An {@link android.graphics.drawable.AnimationDrawable} resource id */ VectorAnimationController(Context context, int animationId) { mAnimatedVectorDrawableCompat = AnimatedVectorDrawableCompat.create(context, animationId); mAnimationCallback = new Animatable2Compat.AnimationCallback() { @Override public void onAnimationEnd(Drawable drawable) { mAnimatedVectorDrawableCompat.start(); } }; } @Override public int getVideoWidth() { return mAnimatedVectorDrawableCompat.getIntrinsicWidth(); } @Override public int getVideoHeight() { return mAnimatedVectorDrawableCompat.getIntrinsicHeight(); } @Override public void pause() { mAnimatedVectorDrawableCompat.stop(); } @Override public void start() { mAnimatedVectorDrawableCompat.start(); } @Override public boolean isPlaying() { return mAnimatedVectorDrawableCompat.isRunning(); } @Override public int getDuration() { // We can't get duration from AnimatedVectorDrawable, just return a non zero value. return 5000; } @Override public void attachView(TextureView video, View preview, View playButton) { mPreviewDrawable = preview.getForeground(); video.setVisibility(View.GONE); updateViewStates(preview, playButton); preview.setOnClickListener(v -> updateViewStates(preview, playButton)); } @Override public void release() { mAnimatedVectorDrawableCompat.stop(); mAnimatedVectorDrawableCompat.clearAnimationCallbacks(); } private void updateViewStates(View imageView, View playButton) { if (mAnimatedVectorDrawableCompat.isRunning()) { mAnimatedVectorDrawableCompat.stop(); mAnimatedVectorDrawableCompat.clearAnimationCallbacks(); playButton.setVisibility(View.VISIBLE); imageView.setForeground(mPreviewDrawable); } else { playButton.setVisibility(View.GONE); imageView.setForeground(mAnimatedVectorDrawableCompat); mAnimatedVectorDrawableCompat.start(); mAnimatedVectorDrawableCompat.registerAnimationCallback(mAnimationCallback); } } }
src/com/android/settings/widget/VideoPreference.java +81 −116 Original line number Diff line number Diff line Loading @@ -16,16 +16,11 @@ package com.android.settings.widget; import android.content.ContentResolver; import android.content.Context; import android.content.res.TypedArray; import android.graphics.SurfaceTexture; import android.media.MediaPlayer; import android.net.Uri; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.ImageView; Loading @@ -34,30 +29,27 @@ import android.widget.LinearLayout; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat; import com.android.settings.R; /** * A full width preference that hosts a MP4 video. * A full width preference that hosts a MP4 video or a {@link AnimatedVectorDrawableCompat}. */ public class VideoPreference extends Preference { private static final String TAG = "VideoPreference"; private final Context mContext; private Uri mVideoPath; @VisibleForTesting MediaPlayer mMediaPlayer; AnimationController mAnimationController; @VisibleForTesting boolean mAnimationAvailable; @VisibleForTesting boolean mVideoReady; private boolean mVideoPaused; private float mAspectRatio = 1.0f; private int mPreviewResource; private boolean mViewVisible; private Surface mSurface; private int mPreviewId; private int mAnimationId; private int mVectorAnimationId; private int mHeight = LinearLayout.LayoutParams.MATCH_PARENT - 1; // video height in pixels public VideoPreference(Context context) { Loading @@ -84,19 +76,17 @@ public class VideoPreference extends Preference { mAnimationId = mAnimationId == 0 ? attributes.getResourceId(R.styleable.VideoPreference_animation, 0) : mAnimationId; mVideoPath = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) .authority(context.getPackageName()) .appendPath(String.valueOf(mAnimationId)) .build(); mPreviewResource = mPreviewResource == 0 mPreviewId = mPreviewId == 0 ? attributes.getResourceId(R.styleable.VideoPreference_preview, 0) : mPreviewResource; if (mPreviewResource == 0 && mAnimationId == 0) { : mPreviewId; mVectorAnimationId = attributes.getResourceId( R.styleable.VideoPreference_vectorAnimation, 0); if (mPreviewId == 0 && mAnimationId == 0 && mVectorAnimationId == 0) { setVisible(false); return; } initMediaPlayer(); if (mMediaPlayer != null && mMediaPlayer.getDuration() > 0) { initAnimationController(); if (mAnimationController != null && mAnimationController.getDuration() > 0) { setVisible(true); setLayoutResource(R.layout.video_preference); mAnimationAvailable = true; Loading @@ -120,96 +110,33 @@ public class VideoPreference extends Preference { } final TextureView video = (TextureView) holder.findViewById(R.id.video_texture_view); final ImageView imageView = (ImageView) holder.findViewById(R.id.video_preview_image); final ImageView previewImage = (ImageView) holder.findViewById(R.id.video_preview_image); final ImageView playButton = (ImageView) holder.findViewById(R.id.video_play_button); final AspectRatioFrameLayout layout = (AspectRatioFrameLayout) holder.findViewById( R.id.video_container); imageView.setImageResource(mPreviewResource); previewImage.setImageResource(mPreviewId); layout.setAspectRatio(mAspectRatio); if (mHeight >= LinearLayout.LayoutParams.MATCH_PARENT) { layout.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, mHeight)); } updateViewStates(imageView, playButton); video.setOnClickListener(v -> updateViewStates(imageView, playButton)); video.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { if (mMediaPlayer != null) { mSurface = new Surface(surfaceTexture); mMediaPlayer.setSurface(mSurface); } } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { imageView.setVisibility(View.VISIBLE); return false; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { if (!mViewVisible) { return; } if (mVideoReady) { if (imageView.getVisibility() == View.VISIBLE) { imageView.setVisibility(View.GONE); } if (!mVideoPaused && mMediaPlayer != null && !mMediaPlayer.isPlaying()) { mMediaPlayer.start(); playButton.setVisibility(View.GONE); } } if (mMediaPlayer != null && !mMediaPlayer.isPlaying() && playButton.getVisibility() != View.VISIBLE) { playButton.setVisibility(View.VISIBLE); } } }); } @VisibleForTesting void updateViewStates(ImageView imageView, ImageView playButton) { if (mMediaPlayer != null) { if (mMediaPlayer.isPlaying()) { mMediaPlayer.pause(); playButton.setVisibility(View.VISIBLE); imageView.setVisibility(View.VISIBLE); mVideoPaused = true; } else { imageView.setVisibility(View.GONE); playButton.setVisibility(View.GONE); mMediaPlayer.start(); mVideoPaused = false; } } mAnimationController.attachView(video, previewImage, playButton); } @Override public void onDetached() { releaseMediaPlayer(); releaseAnimationController(); super.onDetached(); } public void onViewVisible(boolean videoPaused) { mViewVisible = true; mVideoPaused = videoPaused; initMediaPlayer(); initAnimationController(); } public void onViewInvisible() { mViewVisible = false; releaseMediaPlayer(); releaseAnimationController(); } /** Loading @@ -221,34 +148,25 @@ public class VideoPreference extends Preference { */ public void setVideo(int videoId, int previewId) { mAnimationId = videoId; mPreviewResource = previewId; releaseMediaPlayer(); mPreviewId = previewId; releaseAnimationController(); initialize(mContext, null); } private void initMediaPlayer() { if (mMediaPlayer == null) { mMediaPlayer = MediaPlayer.create(mContext, mVideoPath); // when the playback res is invalid or others, MediaPlayer create may fail // and return null, so need add the null judgement. if (mMediaPlayer != null) { mMediaPlayer.seekTo(0); mMediaPlayer.setOnSeekCompleteListener(mp -> mVideoReady = true); mMediaPlayer.setOnPreparedListener(mediaPlayer -> mediaPlayer.setLooping(true)); if (mSurface != null) { mMediaPlayer.setSurface(mSurface); } private void initAnimationController() { if (mVectorAnimationId != 0) { mAnimationController = new VectorAnimationController(mContext, mVectorAnimationId); return; } if (mAnimationId != 0) { mAnimationController = new MediaAnimationController(mContext, mAnimationId); } } private void releaseMediaPlayer() { if (mMediaPlayer != null) { mMediaPlayer.stop(); mMediaPlayer.reset(); mMediaPlayer.release(); mMediaPlayer = null; mVideoReady = false; private void releaseAnimationController() { if (mAnimationController != null) { mAnimationController.release(); mAnimationController = null; } } Loading @@ -262,6 +180,7 @@ public class VideoPreference extends Preference { /** * sets the height of the video preference * * @param height in dp */ public void setHeight(float height) { Loading @@ -271,6 +190,52 @@ public class VideoPreference extends Preference { @VisibleForTesting void updateAspectRatio() { mAspectRatio = mMediaPlayer.getVideoWidth() / (float) mMediaPlayer.getVideoHeight(); mAspectRatio = mAnimationController.getVideoWidth() / (float) mAnimationController.getVideoHeight(); } /** * Handle animation operations. */ interface AnimationController { /** * Pauses the animation. */ void pause(); /** * Starts the animation. */ void start(); /** * Releases the animation object. */ void release(); /** * Attaches the animation to UI view. */ void attachView(TextureView video, View preview, View playButton); /** * Returns the animation Width. */ int getVideoWidth(); /** * Returns the animation Height. */ int getVideoHeight(); /** * Returns the animation duration. */ int getDuration(); /** * Returns if the animation is playing. */ boolean isPlaying(); } }
tests/robotests/src/com/android/settings/testutils/shadow/ShadowSettingsMediaPlayer.java 0 → 100644 +51 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.testutils.shadow; import static org.robolectric.shadows.ShadowMediaPlayer.State.INITIALIZED; import android.content.Context; import android.media.MediaPlayer; import android.net.Uri; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowMediaPlayer; import org.robolectric.shadows.util.DataSource; @Implements(MediaPlayer.class) public class ShadowSettingsMediaPlayer extends ShadowMediaPlayer { @Implementation public static MediaPlayer create(Context context, Uri uri) { final DataSource ds = DataSource.toDataSource(context, uri); addMediaInfo(ds, new ShadowMediaPlayer.MediaInfo()); final MediaPlayer mp = new MediaPlayer(); final ShadowMediaPlayer shadow = Shadow.extract(mp); try { shadow.setDataSource(ds); shadow.setState(INITIALIZED); mp.prepare(); } catch (Exception e) { return null; } return mp; } }