From b1bcedc0553c1cdae397455c13556fe9695b985c Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Thu, 3 Jun 2021 20:47:06 +0800 Subject: [PATCH 1/2] Fixing the animation of drawable couldn't play automatically for the banner. Root cause: Only supported AnimatedImageDrawable before. Solution: Support all classes which implement Animatable Bug: 190032215 Test: atest AnimatedImagePreferenceTest Change-Id: Iba18bfab9a46fd02f642d66a3bb9b9ce513c4456 --- .../AnimatedImagePreference.java | 46 ++++++++++++++++--- .../AnimatedImagePreferenceTest.java | 38 ++++++++++++--- 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/com/android/settings/accessibility/AnimatedImagePreference.java b/src/com/android/settings/accessibility/AnimatedImagePreference.java index 2ca13f33fe2..69c0d136cbd 100644 --- a/src/com/android/settings/accessibility/AnimatedImagePreference.java +++ b/src/com/android/settings/accessibility/AnimatedImagePreference.java @@ -17,7 +17,9 @@ package com.android.settings.accessibility; import android.content.Context; -import android.graphics.drawable.AnimatedImageDrawable; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Animatable2; +import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.widget.ImageView; @@ -36,6 +38,14 @@ public class AnimatedImagePreference extends Preference { private Uri mImageUri; private int mMaxHeight = -1; + private final Animatable2.AnimationCallback mAnimationCallback = + new Animatable2.AnimationCallback() { + @Override + public void onAnimationEnd(Drawable drawable) { + ((Animatable2) drawable).start(); + } + }; + AnimatedImagePreference(Context context) { super(context); setLayoutResource(R.layout.preference_animated_image); @@ -51,12 +61,10 @@ public class AnimatedImagePreference extends Preference { } if (mImageUri != null) { - imageView.setImageURI(mImageUri); + resetAnimation(imageView.getDrawable()); - final Drawable drawable = imageView.getDrawable(); - if (drawable instanceof AnimatedImageDrawable) { - ((AnimatedImageDrawable) drawable).start(); - } + imageView.setImageURI(mImageUri); + startAnimation(imageView.getDrawable()); } if (mMaxHeight > -1) { @@ -87,4 +95,30 @@ public class AnimatedImagePreference extends Preference { notifyChanged(); } } + + private void startAnimation(Drawable drawable) { + if (!(drawable instanceof Animatable)) { + return; + } + + if (drawable instanceof Animatable2) { + ((Animatable2) drawable).registerAnimationCallback(mAnimationCallback); + } else if (drawable instanceof AnimationDrawable) { + ((AnimationDrawable) drawable).setOneShot(false); + } + + ((Animatable) drawable).start(); + } + + private void resetAnimation(Drawable drawable) { + if (!(drawable instanceof Animatable)) { + return; + } + + if (drawable instanceof Animatable2) { + ((Animatable2) drawable).clearAnimationCallbacks(); + } + + ((Animatable) drawable).stop(); + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/AnimatedImagePreferenceTest.java b/tests/robotests/src/com/android/settings/accessibility/AnimatedImagePreferenceTest.java index c3dd7b5af33..b8ab432d2e9 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AnimatedImagePreferenceTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AnimatedImagePreferenceTest.java @@ -19,12 +19,15 @@ package com.android.settings.accessibility; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.content.Context; import android.graphics.drawable.AnimatedImageDrawable; +import android.graphics.drawable.AnimatedVectorDrawable; +import android.graphics.drawable.AnimationDrawable; import android.net.Uri; import android.view.LayoutInflater; import android.view.View; @@ -37,7 +40,6 @@ import com.android.settings.R; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import org.robolectric.RobolectricTestRunner; @@ -54,9 +56,6 @@ public class AnimatedImagePreferenceTest { @Spy private ImageView mImageView; - @Mock - private AnimatedImageDrawable mAnimatedImageDrawable; - @Before public void init() { MockitoAnnotations.initMocks(this); @@ -72,14 +71,39 @@ public class AnimatedImagePreferenceTest { } @Test - public void readImageUri_animatedImage_startAnimation() { + public void playAnimation_animatedImageDrawable_success() { + final AnimatedImageDrawable drawable = mock(AnimatedImageDrawable.class); doReturn(mImageView).when(mRootView).findViewById(R.id.animated_img); - doReturn(mAnimatedImageDrawable).when(mImageView).getDrawable(); + doReturn(drawable).when(mImageView).getDrawable(); mAnimatedImagePreference.setImageUri(mImageUri); mAnimatedImagePreference.onBindViewHolder(mViewHolder); - verify(mAnimatedImageDrawable).start(); + verify(drawable).start(); + } + + @Test + public void playAnimation_animatedVectorDrawable_success() { + final AnimatedVectorDrawable drawable = mock(AnimatedVectorDrawable.class); + doReturn(mImageView).when(mRootView).findViewById(R.id.animated_img); + doReturn(drawable).when(mImageView).getDrawable(); + + mAnimatedImagePreference.setImageUri(mImageUri); + mAnimatedImagePreference.onBindViewHolder(mViewHolder); + + verify(drawable).start(); + } + + @Test + public void playAnimation_animationDrawable_success() { + final AnimationDrawable drawable = mock(AnimationDrawable.class); + doReturn(mImageView).when(mRootView).findViewById(R.id.animated_img); + doReturn(drawable).when(mImageView).getDrawable(); + + mAnimatedImagePreference.setImageUri(mImageUri); + mAnimatedImagePreference.onBindViewHolder(mViewHolder); + + verify(drawable).start(); } @Test From 3ce3a93e711ca796a971ad1f6418efc46a2e7abc Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Thu, 3 Jun 2021 23:46:25 +0800 Subject: [PATCH 2/2] Support the lottie image file for the banner in Accessibility Settings. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Action: Currently, the ImageView component couldn’t handle the lottie image from raw resource folder, so temporarily using the LottieAnimationView from settingsLib to support lottie image for the banner of Accessibility settings. Bug: 186065724 Bug: 179451422 Test: atest AnimatedImagePreferenceTest Change-Id: I99fb2ed26085c73bc262c58001de8dec3078e1d0 --- res/layout/preference_animated_image.xml | 10 +++ .../AnimatedImagePreference.java | 63 ++++++++++++++++++- .../AnimatedImagePreferenceTest.java | 33 ++++++++-- 3 files changed, 99 insertions(+), 7 deletions(-) diff --git a/res/layout/preference_animated_image.xml b/res/layout/preference_animated_image.xml index e7d9b52518a..0ab8adf275d 100644 --- a/res/layout/preference_animated_image.xml +++ b/res/layout/preference_animated_image.xml @@ -31,4 +31,14 @@ android:focusable="false" android:clickable="false" android:adjustViewBounds="true"/> + + \ No newline at end of file diff --git a/src/com/android/settings/accessibility/AnimatedImagePreference.java b/src/com/android/settings/accessibility/AnimatedImagePreference.java index 69c0d136cbd..c707e5cc0a7 100644 --- a/src/com/android/settings/accessibility/AnimatedImagePreference.java +++ b/src/com/android/settings/accessibility/AnimatedImagePreference.java @@ -22,6 +22,9 @@ import android.graphics.drawable.Animatable2; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; import android.widget.ImageView; import androidx.preference.Preference; @@ -29,12 +32,20 @@ import androidx.preference.PreferenceViewHolder; import com.android.settings.R; +import com.airbnb.lottie.LottieAnimationView; +import com.airbnb.lottie.LottieDrawable; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.Objects; + /** * A custom {@link ImageView} preference for showing animated or static image, such as animated webp and static png. */ public class AnimatedImagePreference extends Preference { + private static final String TAG = "AnimatedImagePreference"; private Uri mImageUri; private int mMaxHeight = -1; @@ -56,19 +67,27 @@ public class AnimatedImagePreference extends Preference { super.onBindViewHolder(holder); final ImageView imageView = holder.itemView.findViewById(R.id.animated_img); - if (imageView == null) { + final LottieAnimationView lottieView = holder.itemView.findViewById(R.id.lottie_view); + if (imageView == null || lottieView == null) { return; } if (mImageUri != null) { - resetAnimation(imageView.getDrawable()); + resetAnimations(imageView, lottieView); + hideAllChildViews(holder.itemView); imageView.setImageURI(mImageUri); - startAnimation(imageView.getDrawable()); + if (imageView.getDrawable() != null) { + startAnimationWith(imageView); + } else { + // The lottie image from the raw folder also returns null. + startLottieAnimationWith(lottieView); + } } if (mMaxHeight > -1) { imageView.setMaxHeight(mMaxHeight); + lottieView.setMaxHeight(mMaxHeight); } } @@ -96,6 +115,22 @@ public class AnimatedImagePreference extends Preference { } } + private void startAnimationWith(ImageView imageView) { + startAnimation(imageView.getDrawable()); + + imageView.setVisibility(View.VISIBLE); + } + + private void startLottieAnimationWith(LottieAnimationView lottieView) { + final InputStream inputStream = getInputStreamFromUri(mImageUri); + Objects.requireNonNull(inputStream, "Invalid resource."); + lottieView.setAnimation(inputStream, /* cacheKey= */ null); + lottieView.setRepeatCount(LottieDrawable.INFINITE); + lottieView.playAnimation(); + + lottieView.setVisibility(View.VISIBLE); + } + private void startAnimation(Drawable drawable) { if (!(drawable instanceof Animatable)) { return; @@ -110,6 +145,12 @@ public class AnimatedImagePreference extends Preference { ((Animatable) drawable).start(); } + private void resetAnimations(ImageView imageView, LottieAnimationView lottieView) { + resetAnimation(imageView.getDrawable()); + + lottieView.cancelAnimation(); + } + private void resetAnimation(Drawable drawable) { if (!(drawable instanceof Animatable)) { return; @@ -121,4 +162,20 @@ public class AnimatedImagePreference extends Preference { ((Animatable) drawable).stop(); } + + private InputStream getInputStreamFromUri(Uri uri) { + try { + return getContext().getContentResolver().openInputStream(uri); + } catch (FileNotFoundException e) { + Log.w(TAG, "Cannot find content uri: " + uri, e); + return null; + } + } + + private void hideAllChildViews(View itemView) { + final ViewGroup viewGroup = (ViewGroup) itemView; + for (int i = 0; i < viewGroup.getChildCount(); i++) { + viewGroup.getChildAt(i).setVisibility(View.GONE); + } + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/AnimatedImagePreferenceTest.java b/tests/robotests/src/com/android/settings/accessibility/AnimatedImagePreferenceTest.java index b8ab432d2e9..4bce0bb264b 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AnimatedImagePreferenceTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AnimatedImagePreferenceTest.java @@ -18,12 +18,15 @@ package com.android.settings.accessibility; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.content.ContentResolver; import android.content.Context; import android.graphics.drawable.AnimatedImageDrawable; import android.graphics.drawable.AnimatedVectorDrawable; @@ -37,6 +40,8 @@ import androidx.preference.PreferenceViewHolder; import com.android.settings.R; +import com.airbnb.lottie.LottieAnimationView; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,9 +50,12 @@ import org.mockito.Spy; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import java.io.InputStream; + /** Tests for {@link AnimatedImagePreference}. */ @RunWith(RobolectricTestRunner.class) public class AnimatedImagePreferenceTest { + private final Context mContext = RuntimeEnvironment.application; private Uri mImageUri; private View mRootView; private PreferenceViewHolder mViewHolder; @@ -60,13 +68,12 @@ public class AnimatedImagePreferenceTest { public void init() { MockitoAnnotations.initMocks(this); - final Context context = RuntimeEnvironment.application; - final LayoutInflater inflater = LayoutInflater.from(context); + final LayoutInflater inflater = LayoutInflater.from(mContext); mRootView = spy(inflater.inflate(R.layout.preference_animated_image, /* root= */ null)); mViewHolder = spy(PreferenceViewHolder.createInstanceForTests(mRootView)); - mImageView = spy(new ImageView(context)); + mImageView = spy(new ImageView(mContext)); - mAnimatedImagePreference = new AnimatedImagePreference(context); + mAnimatedImagePreference = new AnimatedImagePreference(mContext); mImageUri = new Uri.Builder().build(); } @@ -126,4 +133,22 @@ public class AnimatedImagePreferenceTest { assertThat(mImageView.getMaxHeight()).isEqualTo(maxHeight); } + + @Test + public void setImageUriAndRebindViewHolder_lottieImageFromRawFolder_setAnimation() { + final int fakeLottieResId = 111111; + final Uri lottieImageUri = + new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(mContext.getPackageName()) + .appendPath(String.valueOf(fakeLottieResId)) + .build(); + final LottieAnimationView lottieView = spy(new LottieAnimationView(mContext)); + doReturn(mImageView).when(mRootView).findViewById(R.id.animated_img); + doReturn(lottieView).when(mRootView).findViewById(R.id.lottie_view); + + mAnimatedImagePreference.setImageUri(lottieImageUri); + mAnimatedImagePreference.onBindViewHolder(mViewHolder); + + verify(lottieView).setAnimation(any(InputStream.class), eq(null)); + } }