chromium/chrome/android/java/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataCheckBoxPreference.java

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.browsing_data;

import android.content.Context;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ClickableSpan;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;

import androidx.preference.PreferenceViewHolder;

import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
import org.chromium.components.browser_ui.settings.ChromeBaseCheckBoxPreference;
import org.chromium.ui.text.NoUnderlineClickableSpan;
import org.chromium.ui.text.SpanApplier;

/**
 * A preference representing one browsing data type in ClearBrowsingDataFragment.
 * This class allows clickable links inside the checkbox summary.
 */
public class ClearBrowsingDataCheckBoxPreference extends ChromeBaseCheckBoxPreference {
    private View mView;
    private Runnable mLinkClickDelegate;
    private boolean mHasClickableSpans;

    /** Constructor for inflating from XML. */
    public ClearBrowsingDataCheckBoxPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * @param linkClickDelegate A Runnable that is executed when a link inside the summary is
     *                          clicked.
     */
    public void setLinkClickDelegate(Runnable linkClickDelegate) {
        mLinkClickDelegate = linkClickDelegate;
    }

    @Override
    public void onBindViewHolder(PreferenceViewHolder holder) {
        super.onBindViewHolder(holder);

        mView = holder.itemView;

        final TextView textView = mView.findViewById(android.R.id.summary);

        // Create custom onTouch listener to be able to respond to click events inside the summary.
        textView.setOnTouchListener(
                (View v, MotionEvent event) -> {
                    if (!mHasClickableSpans) {
                        return false;
                    }
                    // Find out which character was touched.
                    int offset = textView.getOffsetForPosition(event.getX(), event.getY());
                    // Check if this character contains a span.
                    CharSequence text = textView.getText();
                    // TODO(crbug.com/40549355): On some devices the SpannableString is not applied
                    // correctly.
                    boolean isSpanned = text instanceof Spanned;
                    if (!isSpanned) {
                        return false;
                    }
                    ClickableSpan[] types =
                            ((Spanned) text).getSpans(offset, offset, ClickableSpan.class);

                    if (types.length > 0) {
                        if (event.getAction() == MotionEvent.ACTION_UP) {
                            for (ClickableSpan type : types) {
                                type.onClick(textView);
                            }
                        }
                        return true;
                    } else {
                        return false;
                    }
                });
    }

    public void announceForAccessibility(CharSequence announcement) {
        if (mView != null) mView.announceForAccessibility(announcement);
    }

    @Override
    public void setSummary(CharSequence summary) {
        // If there is no link in the summary invoke the default behavior.
        String summaryString = summary.toString();
        if (!summaryString.contains("<link>") || !summaryString.contains("</link>")) {
            super.setSummary(summary);
            return;
        }
        // Talkback users can't select links inside the summary because it is already a target
        // that toggles the checkbox. Links will still be read out and users can manually
        // navigate to them.
        if (ChromeAccessibilityUtil.get().isAccessibilityEnabled()) {
            super.setSummary(summaryString.replaceAll("</?link>", ""));
            return;
        }
        // Linkify <link></link> span.
        final SpannableString summaryWithLink =
                SpanApplier.applySpans(
                        summaryString,
                        new SpanApplier.SpanInfo(
                                "<link>",
                                "</link>",
                                new NoUnderlineClickableSpan(
                                        getContext(),
                                        (widget) -> {
                                            if (mLinkClickDelegate != null) {
                                                mLinkClickDelegate.run();
                                            }
                                        })));

        mHasClickableSpans = true;
        super.setSummary(summaryWithLink);
    }
}