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)); + } }