ProtoLayoutThemeImpl.java
/*
* Copyright 2023 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 androidx.wear.protolayout.renderer.inflater;
import static androidx.core.util.Preconditions.checkNotNull;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.util.TypedValue;
import androidx.annotation.AttrRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import androidx.annotation.StyleableRes;
import androidx.collection.ArrayMap;
import androidx.wear.protolayout.proto.LayoutElementProto.FontVariant;
import androidx.wear.protolayout.renderer.ProtoLayoutTheme;
import androidx.wear.protolayout.renderer.R;
import java.util.Map;
/** Theme customization for ProtoLayout texts, which includes Font types and variants. */
public class ProtoLayoutThemeImpl implements ProtoLayoutTheme {
/** Holder for different weights of the same font variant. */
public static class FontSetImpl implements FontSet {
final Typeface mNormalFont;
final Typeface mMediumFont;
final Typeface mBoldFont;
FontSetImpl(@NonNull Theme theme, @StyleRes int style) {
TypedArray a = theme.obtainStyledAttributes(style, R.styleable.ProtoLayoutFontSet);
this.mNormalFont =
loadTypeface(a, R.styleable.ProtoLayoutFontSet_protoLayoutNormalFont);
this.mMediumFont =
loadTypeface(a, R.styleable.ProtoLayoutFontSet_protoLayoutMediumFont);
this.mBoldFont = loadTypeface(a, R.styleable.ProtoLayoutFontSet_protoLayoutBoldFont);
a.recycle();
}
private static Typeface loadTypeface(TypedArray array, @StyleableRes int styleableResId) {
// Resources are a little nasty; we can't just check if resType =
// TypedValue.TYPE_REFERENCE, because it never is (if you use @font/foo inside of
// styles.xml, the value will be a string of the form res/font/foo.ttf). Instead, see if
// there's a resource ID at all, and use that, otherwise assume it's a well known font
// family.
int resType = array.getType(styleableResId);
if (array.getResourceId(styleableResId, -1) != -1
&& array.getFont(styleableResId) != null) {
return checkNotNull(array.getFont(styleableResId));
} else if (resType == TypedValue.TYPE_STRING
&& array.getString(styleableResId) != null) {
// Load the normal typeface; we customise this into BOLD/ITALIC later in
// ProtoLayoutRenderer.
return Typeface.create(
checkNotNull(array.getString(styleableResId)), Typeface.NORMAL);
} else {
throw new IllegalArgumentException("Unknown resource value type " + resType);
}
}
@Override
@NonNull
public Typeface getNormalFont() {
return mNormalFont;
}
@Override
@NonNull
public Typeface getMediumFont() {
return mMediumFont;
}
@Override
@NonNull
public Typeface getBoldFont() {
return mBoldFont;
}
}
/**
* Creates a ProtoLayoutTheme for the default theme, based on R.style.ProtoLayoutBaseTheme and
* R.attr.protoLayoutFallbackAppearance from the local package.
*/
@NonNull
public static ProtoLayoutTheme defaultTheme(@NonNull Context context) {
return new ProtoLayoutThemeImpl(context.getResources(), R.style.ProtoLayoutBaseTheme);
}
/**
* Creates a ProtoLayoutTheme for the default theme, based on R.style.ProtoLayoutBaseTheme and
* R.attr.protoLayoutFallbackAppearance from the local package.
*/
@NonNull
public static ProtoLayoutTheme defaultTheme(@NonNull Resources resources) {
return new ProtoLayoutThemeImpl(resources, R.style.ProtoLayoutBaseTheme);
}
private final Map<Integer, FontSet> mVariantToFontSet = new ArrayMap<>();
private final Theme mTheme;
@AttrRes private final int mFallbackTextAppearanceAttrId;
/** Constructor with default fallbackTextAppearanceAttrId. */
public ProtoLayoutThemeImpl(@NonNull Context context, @StyleRes int themeResId) {
this(context.getResources(), themeResId, R.attr.protoLayoutFallbackTextAppearance);
}
/** Constructor with default fallbackTextAppearanceAttrId. */
public ProtoLayoutThemeImpl(@NonNull Resources resources, @StyleRes int themeResId) {
this(resources, themeResId, R.attr.protoLayoutFallbackTextAppearance);
}
/**
* Constructor.
*
* @param resources Resources reference containing the styles.
* @param themeResId a Style resource id for ProtoLayoutTheme that can be read from the
* specified resources.
* @param fallbackTextAppearanceAttrId a attribute id for the fallbackTextAppearance that can be
* read from the specified resources
*/
public ProtoLayoutThemeImpl(
@NonNull Resources resources,
@StyleRes int themeResId,
@AttrRes int fallbackTextAppearanceAttrId) {
mTheme = resources.newTheme();
mTheme.applyStyle(themeResId, true);
mFallbackTextAppearanceAttrId = fallbackTextAppearanceAttrId;
TypedArray a = mTheme.obtainStyledAttributes(R.styleable.ProtoLayoutTheme);
mVariantToFontSet.put(
FontVariant.FONT_VARIANT_TITLE_VALUE,
new FontSetImpl(
mTheme,
a.getResourceId(R.styleable.ProtoLayoutTheme_protoLayoutTitleFont, -1)));
mVariantToFontSet.put(
FontVariant.FONT_VARIANT_BODY_VALUE,
new FontSetImpl(
mTheme,
a.getResourceId(R.styleable.ProtoLayoutTheme_protoLayoutBodyFont, -1)));
a.recycle();
}
/**
* Gets the FontSet for a given font variant.
*
* @param fontVariant the numeric value of the proto enum {@link FontVariant}.
*/
@Override
@NonNull
public FontSet getFontSet(int fontVariant) {
FontSet defaultFontSet =
checkNotNull(mVariantToFontSet.get(FontVariant.FONT_VARIANT_BODY_VALUE));
return mVariantToFontSet.getOrDefault(fontVariant, defaultFontSet);
}
/** Gets an Android Theme object styled with TextAppearance attributes. */
@Override
@NonNull
public Theme getTheme() {
return mTheme;
}
/**
* Gets a Attribute resource Id for a fallback TextAppearance. The resource with this id should
* be present in the Android Theme returned by {@link ProtoLayoutTheme#getTheme()}.
*/
@Override
@AttrRes
public int getFallbackTextAppearanceResId() {
return mFallbackTextAppearanceAttrId;
}
@Override
@DrawableRes
public int getRippleResId() {
return 0;
}
}