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

Commit 241cfec9 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Update animation controls" into main

parents fbfa2a66 f8ad2e81
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -18,6 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.android.settingslib.widget.preference.illustration">

    <uses-sdk android:minSdkVersion="28" />
    <uses-sdk android:minSdkVersion="30" />

</manifest>
+27 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="UTF-8"?>
<!--
  Copyright (C) 2025 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.
  -->

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <!-- Label for an accessibility action that starts an animation [CHAR LIMIT=30] -->
    <string name="settingslib_action_label_resume">resume</string>
    <!-- Label for an accessibility action that stops an animation [CHAR LIMIT=30] -->
    <string name="settingslib_action_label_pause">pause</string>
    <!-- Label for an accessibility action that stops an animation [CHAR LIMIT=50] -->
    <string name="settingslib_state_animation_playing">Animation playing</string>
    <!-- Label for an accessibility action that stops an animation [CHAR LIMIT=50] -->
    <string name="settingslib_state_animation_paused">Animation paused</string>
</resources>
+76 −17
Original line number Diff line number Diff line
@@ -32,6 +32,8 @@ import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.FrameLayout;
import android.widget.ImageView;

@@ -73,6 +75,7 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
    private boolean mLottieDynamicColor;
    private CharSequence mContentDescription;
    private boolean mIsTablet;
    private boolean mIsAnimatable;
    private boolean mIsAnimationPaused;

    /**
@@ -81,6 +84,7 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
    public interface OnBindListener {
        /**
         * Called when when {@link #onBindViewHolder(PreferenceViewHolder)} occurs.
         *
         * @param animationView the animation view for this preference.
         */
        void onBind(LottieAnimationView animationView);
@@ -144,16 +148,6 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
                (FrameLayout) holder.findViewById(R.id.middleground_layout);
        final LottieAnimationView illustrationView =
                (LottieAnimationView) holder.findViewById(R.id.lottie_view);
        // Pause and resume animation
        illustrationFrame.setOnClickListener(v -> {
            mIsAnimationPaused = !mIsAnimationPaused;
            if (mIsAnimationPaused) {
                illustrationView.pauseAnimation();
            } else {
                illustrationView.resumeAnimation();
            }
        });

        if (illustrationView != null && !TextUtils.isEmpty(mContentDescription)) {
            illustrationView.setContentDescription(mContentDescription);
            illustrationView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -171,6 +165,7 @@ public class IllustrationPreference extends Preference implements GroupSectionDi

        illustrationView.setCacheComposition(mCacheComposition);
        handleImageWithAnimation(illustrationView, illustrationFrame);
        handleAnimationControl(illustrationView, illustrationFrame);
        handleImageFrameMaxHeight(backgroundView, illustrationView);

        if (mIsAutoScale) {
@@ -377,6 +372,7 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
            final Drawable drawable = illustrationView.getDrawable();
            if (drawable != null) {
                startAnimation(drawable);
                mIsAnimatable = false;
            }
        }

@@ -386,10 +382,12 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
            final Drawable drawable = illustrationView.getDrawable();
            if (drawable != null) {
                startAnimation(drawable);
                mIsAnimatable = false;
            } else {
                // The lottie image from the raw folder also returns null because the ImageView
                // couldn't handle it now.
                startLottieAnimationWith(illustrationView, mImageUri);
                mIsAnimatable = true;
            }
        }

@@ -418,10 +416,12 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
            final Drawable drawable = illustrationView.getDrawable();
            if (drawable != null) {
                startAnimation(drawable);
                mIsAnimatable = false;
            } else {
                // The lottie image from the raw folder also returns null because the ImageView
                // couldn't handle it now.
                startLottieAnimationWith(illustrationView, mImageResId);
                mIsAnimatable = true;
            }
        }
    }
@@ -459,6 +459,60 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
        ((Animatable) drawable).start();
    }

    private void handleAnimationControl(LottieAnimationView illustrationView,
            ViewGroup container) {
        if (mIsAnimatable) {
            // TODO(b/397340540): list out pages having illustration without a content description.
            if (TextUtils.isEmpty(mContentDescription)) {
                Log.w(TAG, "Illustration should have a content description. preference key = "
                        + getKey());
            }
            // Enable pause and resume abilities to animation only
            container.setOnClickListener(v -> {
                mIsAnimationPaused = !mIsAnimationPaused;
                if (mIsAnimationPaused) {
                    illustrationView.pauseAnimation();
                } else {
                    illustrationView.resumeAnimation();
                }
                updateAccessibilityAction(container);
            });

            updateAccessibilityAction(container);
        }
    }

    private void updateAccessibilityAction(ViewGroup container) {
        // Setting the state of animation
        container.setStateDescription(getStateDescriptionForAnimation());
        container.setAccessibilityDelegate(new View.AccessibilityDelegate() {
            @Override
            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
                super.onInitializeAccessibilityNodeInfo(host, info);
                final AccessibilityAction clickAction = new AccessibilityAction(
                        AccessibilityNodeInfo.ACTION_CLICK,
                        getActionLabelForAnimation());
                info.addAction(clickAction);
            }
        });
    }

    private String getActionLabelForAnimation() {
        if (mIsAnimationPaused) {
            return getContext().getString(R.string.settingslib_action_label_resume);
        } else {
            return getContext().getString(R.string.settingslib_action_label_pause);
        }
    }

    private String getStateDescriptionForAnimation() {
        if (mIsAnimationPaused) {
            return getContext().getString(R.string.settingslib_state_animation_paused);
        } else {
            return getContext().getString(R.string.settingslib_state_animation_playing);
        }
    }

    private static void startLottieAnimationWith(LottieAnimationView illustrationView,
            Uri imageUri) {
        final InputStream inputStream =
@@ -514,15 +568,20 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
        mIsAutoScale = false;
        if (attrs != null) {
            TypedArray a = context.obtainStyledAttributes(attrs,
                    com.airbnb.lottie.R.styleable.LottieAnimationView, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
            mImageResId = a.getResourceId(com.airbnb.lottie.R.styleable.LottieAnimationView_lottie_rawRes, 0);
                    com.airbnb.lottie.R.styleable.LottieAnimationView, /* defStyleAttr= */ 0,
                    /* defStyleRes= */ 0);
            mImageResId = a.getResourceId(
                    com.airbnb.lottie.R.styleable.LottieAnimationView_lottie_rawRes,
                    /* defValue= */ 0);
            mCacheComposition = a.getBoolean(
                    com.airbnb.lottie.R.styleable.LottieAnimationView_lottie_cacheComposition, true);
                    com.airbnb.lottie.R.styleable.LottieAnimationView_lottie_cacheComposition,
                    /* defValue= */ true);

            a = context.obtainStyledAttributes(attrs,
                    R.styleable.IllustrationPreference, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
                    R.styleable.IllustrationPreference, /* defStyleAttr= */ 0,
                    /* defStyleRes= */ 0);
            mLottieDynamicColor = a.getBoolean(R.styleable.IllustrationPreference_dynamicColor,
                    false);
                    /* defValue= */ false);

            a.recycle();
        }