Loading packages/SettingsLib/IllustrationPreference/AndroidManifest.xml +1 −1 Original line number Diff line number Diff line Loading @@ -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> packages/SettingsLib/IllustrationPreference/res/values/strings.xml 0 → 100644 +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> packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java +76 −17 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; /** Loading @@ -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); Loading Loading @@ -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); Loading @@ -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) { Loading Loading @@ -377,6 +372,7 @@ public class IllustrationPreference extends Preference implements GroupSectionDi final Drawable drawable = illustrationView.getDrawable(); if (drawable != null) { startAnimation(drawable); mIsAnimatable = false; } } Loading @@ -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; } } Loading Loading @@ -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; } } } Loading Loading @@ -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 = Loading Loading @@ -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(); } Loading Loading
packages/SettingsLib/IllustrationPreference/AndroidManifest.xml +1 −1 Original line number Diff line number Diff line Loading @@ -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>
packages/SettingsLib/IllustrationPreference/res/values/strings.xml 0 → 100644 +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>
packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java +76 −17 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; /** Loading @@ -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); Loading Loading @@ -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); Loading @@ -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) { Loading Loading @@ -377,6 +372,7 @@ public class IllustrationPreference extends Preference implements GroupSectionDi final Drawable drawable = illustrationView.getDrawable(); if (drawable != null) { startAnimation(drawable); mIsAnimatable = false; } } Loading @@ -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; } } Loading Loading @@ -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; } } } Loading Loading @@ -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 = Loading Loading @@ -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(); } Loading