Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1a77fce1 authored by PETER LIANG's avatar PETER LIANG Committed by Android (Google) Code Review
Browse files

Merge "Replace the AnimatedImagePreference with IllustrationPreference." into sc-dev

parents f8d5f8ca 28114281
Loading
Loading
Loading
Loading
+0 −45
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  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.
  -->

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/settingslib_illustration_height"
    android:gravity="center"
    android:padding="@dimen/settingslib_illustration_padding"
    android:orientation="vertical"
    android:importantForAccessibility="noHideDescendants">

    <ImageView
        android:id="@+id/animated_img"
        android:maxWidth="@dimen/settingslib_illustration_width"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:background="@drawable/protection_background"
        android:scaleType="fitCenter"
        android:adjustViewBounds="true"/>

    <com.airbnb.lottie.LottieAnimationView
        android:id="@+id/lottie_view"
        android:maxWidth="@dimen/settingslib_illustration_width"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:background="@drawable/protection_background"
        android:scaleType="fitCenter"
        android:adjustViewBounds="true"
        android:clipToOutline="true"/>
</LinearLayout>
 No newline at end of file
+0 −181
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.accessibility;

import android.content.Context;
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.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import androidx.preference.Preference;
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 <a
 * href="https://developers.google.com/speed/webp/">animated webp</a> and static png.
 */
public class AnimatedImagePreference extends Preference {

    private static final String TAG = "AnimatedImagePreference";
    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);
    }

    @Override
    public void onBindViewHolder(PreferenceViewHolder holder) {
        super.onBindViewHolder(holder);

        final ImageView imageView = holder.itemView.findViewById(R.id.animated_img);
        final LottieAnimationView lottieView = holder.itemView.findViewById(R.id.lottie_view);
        if (imageView == null || lottieView == null) {
            return;
        }

        if (mImageUri != null) {
            resetAnimations(imageView, lottieView);
            hideAllChildViews(holder.itemView);

            imageView.setImageURI(mImageUri);
            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);
        }
    }

    /**
     * Sets image uri to display image in {@link ImageView}
     *
     * @param imageUri the Uri of an image
     */
    public void setImageUri(Uri imageUri) {
        if (imageUri != null && !imageUri.equals(mImageUri)) {
            mImageUri = imageUri;
            notifyChanged();
        }
    }

    /**
     * Sets the maximum height of the view.
     *
     * @param maxHeight the maximum height of ImageView in terms of pixels.
     */
    public void setMaxHeight(int maxHeight) {
        if (maxHeight != mMaxHeight) {
            mMaxHeight = maxHeight;
            notifyChanged();
        }
    }

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

        if (drawable instanceof Animatable2) {
            ((Animatable2) drawable).registerAnimationCallback(mAnimationCallback);
        } else if (drawable instanceof AnimationDrawable) {
            ((AnimationDrawable) drawable).setOneShot(false);
        }

        ((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;
        }

        if (drawable instanceof Animatable2) {
            ((Animatable2) drawable).clearAnimationCallbacks();
        }

        ((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);
        }
    }
}
+8 −9
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settings.widget.SettingsMainSwitchPreference;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.accessibility.AccessibilityUtils;
import com.android.settingslib.widget.IllustrationPreference;
import com.android.settingslib.widget.OnMainSwitchChangeListener;

import com.google.android.setupcompat.util.WizardManagerHelper;
@@ -398,15 +399,13 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
            return;
        }

        final int screenHalfHeight = AccessibilityUtil.getScreenHeightPixels(getPrefContext()) / 2;
        final AnimatedImagePreference animatedImagePreference =
                new AnimatedImagePreference(getPrefContext());
        animatedImagePreference.setImageUri(mImageUri);
        animatedImagePreference.setSelectable(false);
        animatedImagePreference.setMaxHeight(screenHalfHeight);
        animatedImagePreference.setKey(KEY_ANIMATED_IMAGE);

        getPreferenceScreen().addPreference(animatedImagePreference);
        final IllustrationPreference illustrationPreference =
                new IllustrationPreference(getPrefContext());
        illustrationPreference.setImageUri(mImageUri);
        illustrationPreference.setSelectable(false);
        illustrationPreference.setKey(KEY_ANIMATED_IMAGE);

        getPreferenceScreen().addPreference(illustrationPreference);
    }

    private void initToggleServiceSwitchPreference() {
+0 −155
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.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;
import android.graphics.drawable.AnimationDrawable;
import android.net.Uri;
import android.view.ViewGroup;
import android.widget.ImageView;

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;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
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 PreferenceViewHolder mViewHolder;
    private AnimatedImagePreference mAnimatedImagePreference;

    @Mock
    private ViewGroup mRootView;

    @Spy
    private ImageView mImageView;

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);

        mViewHolder = spy(PreferenceViewHolder.createInstanceForTests(mRootView));
        doReturn(new LottieAnimationView(mContext)).when(mRootView).findViewById(R.id.lottie_view);
        mImageView = spy(new ImageView(mContext));

        mAnimatedImagePreference = new AnimatedImagePreference(mContext);
        mImageUri = new Uri.Builder().build();
    }

    @Test
    public void playAnimation_animatedImageDrawable_success() {
        final AnimatedImageDrawable drawable = mock(AnimatedImageDrawable.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_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
    public void setImageUri_viewNotExist_setFail() {
        doReturn(null).when(mRootView).findViewById(R.id.animated_img);

        mAnimatedImagePreference.setImageUri(mImageUri);
        mAnimatedImagePreference.onBindViewHolder(mViewHolder);

        verify(mImageView, never()).setImageURI(mImageUri);
    }

    @Test
    public void setMaxHeight_success() {
        final int maxHeight = 100;
        doReturn(mImageView).when(mRootView).findViewById(R.id.animated_img);

        mAnimatedImagePreference.setMaxHeight(maxHeight);
        mAnimatedImagePreference.onBindViewHolder(mViewHolder);

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