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

Commit 4ef18e05 authored by Ibrahim Yilmaz's avatar Ibrahim Yilmaz Committed by Android (Google) Code Review
Browse files

Merge "[API Abuse - Text Consistency] Eliminate zero width, invisible...

Merge "[API Abuse - Text Consistency] Eliminate zero width, invisible formatting chars  and consecutive spaces" into main
parents bbbb2d8f 78fc4a3b
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -114,7 +114,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ContrastColorUtil;
import com.android.internal.util.NewlineNormalizer;
import com.android.internal.util.NotificationBigTextNormalizer;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -3262,12 +3262,12 @@ public class Notification implements Parcelable
        return cs.toString();
    }
    private static CharSequence cleanUpNewLines(@Nullable CharSequence charSequence) {
    private static CharSequence normalizeBigText(@Nullable CharSequence charSequence) {
        if (charSequence == null) {
            return charSequence;
        }
        return NewlineNormalizer.normalizeNewlines(charSequence.toString());
        return NotificationBigTextNormalizer.normalizeBigText(charSequence.toString());
    }
    private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
@@ -8566,7 +8566,7 @@ public class Notification implements Parcelable
            // Replace the text with the big text, but only if the big text is not empty.
            CharSequence bigTextText = mBuilder.processLegacyText(mBigText);
            if (Flags.cleanUpSpansAndNewLines()) {
                bigTextText = cleanUpNewLines(stripStyling(bigTextText));
                bigTextText = normalizeBigText(stripStyling(bigTextText));
            }
            if (!TextUtils.isEmpty(bigTextText)) {
                p.text(bigTextText);
+0 −39
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.internal.util;


import java.util.regex.Pattern;

/**
 * Utility class that replaces consecutive empty lines with single new line.
 * @hide
 */
public class NewlineNormalizer {

    private static final Pattern MULTIPLE_NEWLINES = Pattern.compile("\\v(\\s*\\v)?");

    // Private constructor to prevent instantiation
    private NewlineNormalizer() {}

    /**
     * Replaces consecutive newlines with a single newline in the input text.
     */
    public static String normalizeNewlines(String text) {
        return MULTIPLE_NEWLINES.matcher(text).replaceAll("\n");
    }
}
+123 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.internal.util;


import android.annotation.NonNull;
import android.os.Trace;

import java.util.regex.Pattern;

/**
 * Utility class that normalizes BigText style Notification content.
 * @hide
 */
public class NotificationBigTextNormalizer {

    private static final Pattern MULTIPLE_NEWLINES = Pattern.compile("\\v(\\s*\\v)?");
    private static final Pattern HORIZONTAL_WHITESPACES = Pattern.compile("\\h+");

    // Private constructor to prevent instantiation
    private NotificationBigTextNormalizer() {}

    /**
     * Normalizes the given text by collapsing consecutive new lines into single one and cleaning
     * up each line by removing zero-width characters, invisible formatting characters, and
     * collapsing consecutive whitespace into single space.
     */
    @NonNull
    public static String normalizeBigText(@NonNull String text) {
        try {
            Trace.beginSection("NotifBigTextNormalizer#normalizeBigText");
            text = MULTIPLE_NEWLINES.matcher(text).replaceAll("\n");
            text = HORIZONTAL_WHITESPACES.matcher(text).replaceAll(" ");
            text = normalizeLines(text);
            return text;
        } finally {
            Trace.endSection();
        }
    }

    /**
     * Normalizes lines in a text by removing zero-width characters, invisible formatting
     * characters, and collapsing consecutive whitespace into single space.
     *
     * <p>
     * This method processes the input text line by line. It eliminates zero-width
     * characters (U+200B to U+200D, U+FEFF, U+034F), invisible formatting
     * characters (U+2060 to U+2065, U+206A to U+206F, U+FFF9 to U+FFFB),
     * and replaces any sequence of consecutive whitespace characters with a single space.
     * </p>
     *
     * <p>
     * Additionally, the method trims trailing whitespace from each line and removes any
     * resulting empty lines.
     * </p>
     */
    @NonNull
    private static String normalizeLines(@NonNull String text) {
        String[] lines = text.split("\n");
        final StringBuilder textSB = new StringBuilder(text.length());
        for (int i = 0; i < lines.length; i++) {
            final String line = lines[i];
            final StringBuilder lineSB = new StringBuilder(line.length());
            boolean spaceSeen = false;
            for (int j = 0; j < line.length(); j++) {
                final char character = line.charAt(j);

                // Skip ZERO WIDTH characters
                if ((character >= '\u200B' && character <= '\u200D')
                        || character == '\uFEFF' || character == '\u034F') {
                    continue;
                }
                // Skip INVISIBLE_FORMATTING_CHARACTERS
                if ((character >= '\u2060' && character <= '\u2065')
                        || (character >= '\u206A' && character <= '\u206F')
                        || (character >= '\uFFF9' && character <= '\uFFFB')) {
                    continue;
                }

                if (isSpace(character)) {
                    // eliminate consecutive spaces....
                    if (!spaceSeen) {
                        lineSB.append(" ");
                    }
                    spaceSeen = true;
                } else {
                    spaceSeen = false;
                    lineSB.append(character);
                }
            }
            // trim line.
            final String currentLine = lineSB.toString().trim();

            // don't add empty lines after trim.
            if (currentLine.length() > 0) {
                if (textSB.length() > 0) {
                    textSB.append("\n");
                }
                textSB.append(currentLine);
            }
        }

        return textSB.toString();
    }

    private static boolean isSpace(char ch) {
        return ch != '\n' && Character.isSpaceChar(ch);
    }
}
+0 −71
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.internal.util;

import static junit.framework.Assert.assertEquals;


import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;

import androidx.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Test for {@link NewlineNormalizer}
 * @hide
 */
@DisabledOnRavenwood(blockedBy = NewlineNormalizer.class)
@RunWith(AndroidJUnit4.class)
public class NewlineNormalizerTest {

    @Rule
    public final RavenwoodRule mRavenwood = new RavenwoodRule();

    @Test
    public void testEmptyInput() {
        assertEquals("", NewlineNormalizer.normalizeNewlines(""));
    }

    @Test
    public void testSingleNewline() {
        assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n"));
    }

    @Test
    public void testMultipleConsecutiveNewlines() {
        assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n\n\n\n\n"));
    }

    @Test
    public void testNewlinesWithSpacesAndTabs() {
        String input = "Line 1\n  \n \t \n\tLine 2";
        // Adjusted expected output to include the tab character
        String expected = "Line 1\n\tLine 2";
        assertEquals(expected, NewlineNormalizer.normalizeNewlines(input));
    }

    @Test
    public void testMixedNewlineCharacters() {
        String input = "Line 1\r\nLine 2\u000BLine 3\fLine 4\u2028Line 5\u2029Line 6";
        String expected = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6";
        assertEquals(expected, NewlineNormalizer.normalizeNewlines(input));
    }
}
+148 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.internal.util;

import static junit.framework.Assert.assertEquals;


import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;

import androidx.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Test for {@link NotificationBigTextNormalizer}
 * @hide
 */
@DisabledOnRavenwood(blockedBy = NotificationBigTextNormalizer.class)
@RunWith(AndroidJUnit4.class)
public class NotificationBigTextNormalizerTest {

    @Rule
    public final RavenwoodRule mRavenwood = new RavenwoodRule();


    @Test
    public void testEmptyInput() {
        assertEquals("", NotificationBigTextNormalizer.normalizeBigText(""));
    }

    @Test
    public void testSingleNewline() {
        assertEquals("", NotificationBigTextNormalizer.normalizeBigText("\n"));
    }

    @Test
    public void testMultipleConsecutiveNewlines() {
        assertEquals("", NotificationBigTextNormalizer.normalizeBigText("\n\n\n\n\n"));
    }

    @Test
    public void testNewlinesWithSpacesAndTabs() {
        String input = "Line 1\n  \n \t \n\tLine 2";
        // Adjusted expected output to include the tab character
        String expected = "Line 1\nLine 2";
        assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input));
    }

    @Test
    public void testMixedNewlineCharacters() {
        String input = "Line 1\r\nLine 2\u000BLine 3\fLine 4\u2028Line 5\u2029Line 6";
        String expected = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6";
        assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input));
    }

    @Test
    public void testConsecutiveSpaces() {
        // Only spaces
        assertEquals("This is a test.", NotificationBigTextNormalizer.normalizeBigText("This"
                + "              is   a                         test."));
        // Zero width characters bw spaces.
        assertEquals("This is a test.", NotificationBigTextNormalizer.normalizeBigText("This"
                + "\u200B \u200B \u200B \u200B \u200B \u200B \u200B \u200Bis\uFEFF \uFEFF \uFEFF"
                + " \uFEFFa \u034F \u034F \u034F \u034F \u034F \u034Ftest."));

        // Invisible formatting characters bw spaces.
        assertEquals("This is a test.", NotificationBigTextNormalizer.normalizeBigText("This"
                + "\u2061 \u2061 \u2061 \u2061 \u2061 \u2061 \u2061 \u2061is\u206E \u206E \u206E"
                + " \u206Ea \uFFFB \uFFFB \uFFFB \uFFFB \uFFFB \uFFFBtest."));
        // Non breakable spaces
        assertEquals("This is a test.", NotificationBigTextNormalizer.normalizeBigText("This"
                + "\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0is\u2005 \u2005 \u2005"
                + " \u2005a\u2005\u2005\u2005 \u2005\u2005\u2005test."));
    }

    @Test
    public void testZeroWidthCharRemoval() {
        // Test each character individually
        char[] zeroWidthChars = { '\u200B', '\u200C', '\u200D', '\uFEFF', '\u034F' };

        for (char c : zeroWidthChars) {
            String input = "Test" + c + "string";
            String expected = "Teststring";
            assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input));
        }
    }

    @Test
    public void testWhitespaceReplacement() {
        assertEquals("This text has horizontal whitespace.",
                NotificationBigTextNormalizer.normalizeBigText(
                        "This\ttext\thas\thorizontal\twhitespace."));
        assertEquals("This text has mixed whitespace.",
                NotificationBigTextNormalizer.normalizeBigText(
                        "This  text  has \u00A0 mixed\u2009whitespace."));
        assertEquals("This text has leading and trailing whitespace.",
                NotificationBigTextNormalizer.normalizeBigText(
                        "\t This text has leading and trailing whitespace. \n"));
    }

    @Test
    public void testInvisibleFormattingCharacterRemoval() {
        // Test each character individually
        char[] invisibleFormattingChars = {
                '\u2060', '\u2061', '\u2062', '\u2063', '\u2064', '\u2065',
                '\u206A', '\u206B', '\u206C', '\u206D', '\u206E', '\u206F',
                '\uFFF9', '\uFFFA', '\uFFFB'
        };

        for (char c : invisibleFormattingChars) {
            String input = "Test " + c + "string";
            String expected = "Test string";
            assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input));
        }
    }
    @Test
    public void testNonBreakSpaceReplacement() {
        // Test each character individually
        char[] nonBreakSpaces = {
                '\u00A0', '\u1680', '\u2000', '\u2001', '\u2002',
                '\u2003', '\u2004', '\u2005', '\u2006', '\u2007',
                '\u2008', '\u2009', '\u200A', '\u202F', '\u205F', '\u3000'
        };

        for (char c : nonBreakSpaces) {
            String input = "Test" + c + "string";
            String expected = "Test string";
            assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input));
        }
    }
}