From 6dff72e816908d8b72a9da2b5a0283dc88fd046c Mon Sep 17 00:00:00 2001 From: simonpoole Date: Tue, 7 Nov 2023 08:06:38 +0100 Subject: [PATCH 1/3] Port original google TypefaceSpan code for backwards compatibility The TypefaceSpan(Typeface) constructor was introduced with Android 9, this commit uses googles current code to provide it a backwards compatible fashion. --- .../de/blau/android/DisambiguationMenu.java | 6 +- .../android/util/LeakyTypefaceStorage.java | 78 +++++++++ .../blau/android/util/TypefaceSpanCompat.java | 164 ++++++++++++++++++ 3 files changed, 245 insertions(+), 3 deletions(-) create mode 100644 src/main/java/de/blau/android/util/LeakyTypefaceStorage.java create mode 100644 src/main/java/de/blau/android/util/TypefaceSpanCompat.java diff --git a/src/main/java/de/blau/android/DisambiguationMenu.java b/src/main/java/de/blau/android/DisambiguationMenu.java index 348c4c1571..f7c5701580 100644 --- a/src/main/java/de/blau/android/DisambiguationMenu.java +++ b/src/main/java/de/blau/android/DisambiguationMenu.java @@ -12,7 +12,6 @@ import android.text.SpannableString; import android.text.Spanned; import android.text.style.ForegroundColorSpan; -import android.text.style.TypefaceSpan; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; @@ -41,6 +40,7 @@ import de.blau.android.tasks.Todo; import de.blau.android.util.Screen; import de.blau.android.util.ThemeUtils; +import de.blau.android.util.TypefaceSpanCompat; import de.blau.android.util.Util; /** @@ -66,7 +66,7 @@ public interface OnMenuItemClickListener { private final List onClickListeners = new ArrayList<>(); private View header; private boolean rtl; - private final TypefaceSpan monospaceSpan; + private final TypefaceSpanCompat monospaceSpan; /** * Create a new menu to disambiguate between nearby objects @@ -76,7 +76,7 @@ public interface OnMenuItemClickListener { public DisambiguationMenu(@NonNull View anchor) { this.anchor = anchor; rtl = Util.isRtlScript(anchor.getContext()); - monospaceSpan = new TypefaceSpan(ResourcesCompat.getFont(anchor.getContext(), R.font.b612mono)); + monospaceSpan = new TypefaceSpanCompat(ResourcesCompat.getFont(anchor.getContext(), R.font.b612mono)); } /** diff --git a/src/main/java/de/blau/android/util/LeakyTypefaceStorage.java b/src/main/java/de/blau/android/util/LeakyTypefaceStorage.java new file mode 100644 index 0000000000..f742c4880e --- /dev/null +++ b/src/main/java/de/blau/android/util/LeakyTypefaceStorage.java @@ -0,0 +1,78 @@ +/* Port for backwards compatible version of TextfaceSpan for pre-Android 9 */ +/* + * Copyright (C) 2017 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 de.blau.android.util; + +import android.graphics.Typeface; +import android.os.Parcel; +import android.os.Process; +import android.util.ArrayMap; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; + +/** + * This class is used for Parceling Typeface object. Note: Typeface object can not be passed over the process boundary. + * + * @hide + */ +public class LeakyTypefaceStorage { + private static final Object sLock = new Object(); + private static final ArrayList sStorage = new ArrayList<>(); + private static final ArrayMap sTypefaceMap = new ArrayMap<>(); + + /** + * Write typeface to parcel. + * + * You can't transfer Typeface to a different process. {@link readTypefaceFromParcel} will return {@code null} if + * the {@link readTypefaceFromParcel} is called in a different process. + * + * @param typeface A {@link Typeface} to be written. + * @param parcel A {@link Parcel} object. + */ + public static void writeTypefaceToParcel(@Nullable Typeface typeface, @NonNull Parcel parcel) { + parcel.writeInt(Process.myPid()); + synchronized (sLock) { + final int id; + final Integer i = sTypefaceMap.get(typeface); + if (i != null) { + id = i.intValue(); + } else { + id = sStorage.size(); + sStorage.add(typeface); + sTypefaceMap.put(typeface, id); + } + parcel.writeInt(id); + } + } + + /** + * Read typeface from parcel. + * + * If the {@link Typeface} was created in another process, this method returns null. + * + * @param parcel A {@link Parcel} object + * @return A {@link Typeface} object. + */ + public static @androidx.annotation.Nullable Typeface readTypefaceFromParcel(@androidx.annotation.NonNull Parcel parcel) { + final int pid = parcel.readInt(); + final int typefaceId = parcel.readInt(); + if (pid != Process.myPid()) { + return null; // The Typeface was created and written in another process. + } + synchronized (sLock) { + return sStorage.get(typefaceId); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/blau/android/util/TypefaceSpanCompat.java b/src/main/java/de/blau/android/util/TypefaceSpanCompat.java new file mode 100644 index 0000000000..01e49e95b5 --- /dev/null +++ b/src/main/java/de/blau/android/util/TypefaceSpanCompat.java @@ -0,0 +1,164 @@ +/* Port for backwards compatible version of TextfaceSpan for pre-Android 9 */ +/* + * Copyright (C) 2006 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 de.blau.android.util; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.os.Parcel; +import android.text.ParcelableSpan; +import android.text.TextPaint; +import android.text.style.MetricAffectingSpan; + +/** + * Span that updates the typeface of the text it's attached to. The TypefaceSpan can be constructed either + * based on a font family or based on a Typeface. When {@link #TypefaceSpan(String)} is used, the previous + * style of the TextView is kept. When {@link #TypefaceSpan(Typeface)} is used, the Typeface + * style replaces the TextView's style. + *

+ * For example, let's consider a TextView with android:textStyle="italic" and a typeface + * created based on a font from resources, with a bold style. When applying a TypefaceSpan based the + * typeface, the text will only keep the bold style, overriding the TextView's textStyle. When applying a + * TypefaceSpan based on a font family: "monospace", the resulted text will keep the italic style. + * + *

+ * Typeface myTypeface = Typeface.create(ResourcesCompat.getFont(context, R.font.acme), Typeface.BOLD);
+ * SpannableString string = new SpannableString("Text with typeface span.");
+ * string.setSpan(new TypefaceSpan(myTypeface), 10, 18, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ * string.setSpan(new TypefaceSpan("monospace"), 19, 22, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ * 
+ * + *
Text with + * TypefaceSpans constructed based on a font from resource and from a font family.
+ */ +public class TypefaceSpanCompat extends MetricAffectingSpan implements ParcelableSpan { + @Nullable + private final String mFamily; + @Nullable + private final Typeface mTypeface; + + /** + * Constructs a {@link TypefaceSpan} based on the font family. The previous style of the TextPaint is kept. If the + * font family is null, the text paint is not modified. + * + * @param family The font family for this typeface. Examples include "monospace", "serif", and "sans-serif" + */ + public TypefaceSpanCompat(@Nullable String family) { + this(family, null); + } + + /** + * Constructs a {@link TypefaceSpan} from a {@link Typeface}. The previous style of the TextPaint is overridden and + * the style of the typeface is used. + * + * @param typeface the typeface + */ + public TypefaceSpanCompat(@NonNull Typeface typeface) { + this(null, typeface); + } + + /** + * Constructs a {@link TypefaceSpan} from a parcel. + */ + public TypefaceSpanCompat(@NonNull Parcel src) { + mFamily = src.readString(); + mTypeface = LeakyTypefaceStorage.readTypefaceFromParcel(src); + } + + private TypefaceSpanCompat(@Nullable String family, @Nullable Typeface typeface) { + mFamily = family; + mTypeface = typeface; + } + + @Override + public int getSpanTypeId() { + return 13 /* android.text.TextUtils.TYPEFACE_SPAN */; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mFamily); + LeakyTypefaceStorage.writeTypefaceToParcel(mTypeface, dest); + } + + /** + * Returns the font family name set in the span. + * + * @return the font family name + * @see #TypefaceSpan(String) + */ + @Nullable + public String getFamily() { + return mFamily; + } + + /** + * Returns the typeface set in the span. + * + * @return the typeface set + * @see #TypefaceSpan(Typeface) + */ + @Nullable + public Typeface getTypeface() { + return mTypeface; + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + updateTypeface(ds); + } + + @Override + public void updateMeasureState(@NonNull TextPaint paint) { + updateTypeface(paint); + } + + private void updateTypeface(@NonNull Paint paint) { + if (mTypeface != null) { + paint.setTypeface(mTypeface); + } else if (mFamily != null) { + applyFontFamily(paint, mFamily); + } + } + + private void applyFontFamily(@NonNull Paint paint, @NonNull String family) { + int style; + Typeface old = paint.getTypeface(); + if (old == null) { + style = Typeface.NORMAL; + } else { + style = old.getStyle(); + } + final Typeface styledTypeface = Typeface.create(family, style); + int fake = style & ~styledTypeface.getStyle(); + if ((fake & Typeface.BOLD) != 0) { + paint.setFakeBoldText(true); + } + if ((fake & Typeface.ITALIC) != 0) { + paint.setTextSkewX(-0.25f); + } + paint.setTypeface(styledTypeface); + } + + @Override + public String toString() { + return "TypefaceSpan{" + "family='" + getFamily() + '\'' + ", typeface=" + getTypeface() + '}'; + } +} \ No newline at end of file From b83bc6759b4faab51e1201deadf99e3f402d607e Mon Sep 17 00:00:00 2001 From: simonpoole Date: Tue, 7 Nov 2023 08:17:04 +0100 Subject: [PATCH 2/3] Add attribution for AOSP derived code --- src/main/assets/LICENSE.txt | 2 ++ .../java/de/blau/android/util/LeakyTypefaceStorage.java | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/src/main/assets/LICENSE.txt b/src/main/assets/LICENSE.txt index 4848e0125c..42fc393a1f 100644 --- a/src/main/assets/LICENSE.txt +++ b/src/main/assets/LICENSE.txt @@ -59,6 +59,8 @@ The GeoJson and Turf classes are Copyright (c) 2016 Mapbox licensed on MIT terms The google gson library is licensed under the Apache License, version 2. +TypefaceSpanCompat and LeakyTypefaceStorage are derived from the corresponding AOSP code licensed under the Apache License, version 2. + Roulette Wheel by Bakunetsu Kaito from the Noun Project The material design lightbulb from Austin Andrews SIL Open Font License 1.1 diff --git a/src/main/java/de/blau/android/util/LeakyTypefaceStorage.java b/src/main/java/de/blau/android/util/LeakyTypefaceStorage.java index f742c4880e..1e977ba45f 100644 --- a/src/main/java/de/blau/android/util/LeakyTypefaceStorage.java +++ b/src/main/java/de/blau/android/util/LeakyTypefaceStorage.java @@ -32,6 +32,14 @@ public class LeakyTypefaceStorage { private static final ArrayList sStorage = new ArrayList<>(); private static final ArrayMap sTypefaceMap = new ArrayMap<>(); + + /** + * Private constructor to stop instantiation + */ + private LeakyTypefaceStorage() { + // nothing + } + /** * Write typeface to parcel. * From e6df11b7d94169a7f1fd399d92e8886f473ac4a3 Mon Sep 17 00:00:00 2001 From: simonpoole Date: Tue, 7 Nov 2023 11:40:34 +0100 Subject: [PATCH 3/3] Increase test timeout --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c893961da1..b9d6d08f89 100644 --- a/build.gradle +++ b/build.gradle @@ -752,7 +752,7 @@ task jacocoTestReport(type:JacocoReport, dependsOn: "testCurrentDebugUnitTest") // see https://marathonlabs.github.io/marathon/doc/configuration.html marathon { uncompletedTestRetryQuota = 3 - testOutputTimeoutMillis = 360000 + testOutputTimeoutMillis = 720000 poolingStrategy { operatingSystem = true }