chromium/chrome/browser/ui/android/pdf/java/src/org/chromium/chrome/browser/pdf/PdfCoordinator.java

// Copyright 2024 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.pdf;

import android.app.Activity;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.pdf.viewer.fragment.PdfViewerFragment;

import org.chromium.base.Log;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.components.browser_ui.styles.ChromeColors;

/** The class responsible for setting up PdfPage. */
public class PdfCoordinator {
    private static final String TAG = "PdfCoordinator";
    private static boolean sSkipLoadPdfForTesting;
    private final View mView;
    private final FragmentManager mFragmentManager;

    /** The filepath of the pdf. It is null before download complete. */
    private String mPdfFilePath;

    /**
     * Whether the pdf has been loaded, despite of success or failure. This is used to ensure we
     * load the pdf at most once.
     */
    private boolean mIsPdfLoaded;

    private ChromePdfViewerFragment mChromePdfViewerFragment;

    /**
     * Creates a PdfCoordinator for the PdfPage.
     *
     * @param profile The current Profile.
     * @param activity The current Activity.
     * @param filepath The pdf filepath.
     */
    public PdfCoordinator(Profile profile, Activity activity, String filepath) {
        mView = LayoutInflater.from(activity).inflate(R.layout.pdf_page, null);
        mView.setBackgroundColor(
                ChromeColors.getPrimaryBackgroundColor(activity, profile.isOffTheRecord()));
        mView.addOnAttachStateChangeListener(
                new View.OnAttachStateChangeListener() {
                    @Override
                    public void onViewAttachedToWindow(View view) {
                        loadPdfFile();
                    }

                    @Override
                    public void onViewDetachedFromWindow(View view) {}
                });
        mFragmentManager = ((FragmentActivity) activity).getSupportFragmentManager();
        // Create PdfViewerFragment to start showing the loading spinner.
        mChromePdfViewerFragment = new ChromePdfViewerFragment(mView);
        loadPdfFile(filepath);
    }

    /** The class responsible for rendering pdf document. */
    public static class ChromePdfViewerFragment extends PdfViewerFragment {
        /** Whether the pdf has been loaded successfully. */
        boolean mIsLoadDocumentSuccess;

        /** A unique id to identity the FragmentContainerView in the current PdfPage. */
        int mFragmentContainerViewId;

        public ChromePdfViewerFragment(View containerView) {
            super();
            View fragmentContainerView = containerView.findViewById(R.id.pdf_fragment_container);
            mFragmentContainerViewId = View.generateViewId();
            fragmentContainerView.setId(mFragmentContainerViewId);
        }

        @Override
        public void onLoadDocumentSuccess() {
            mIsLoadDocumentSuccess = true;
            // TODO: capture metrics
        }

        @Override
        public void onLoadDocumentError(@NonNull Throwable throwable) {
            // TODO: capture metrics
        }
    }

    /** Returns the intended view for PdfPage tab. */
    View getView() {
        return mView;
    }

    /**
     * Show pdf specific find in page UI.
     *
     * @return whether the pdf specific find in page UI is shown.
     */
    boolean findInPage() {
        if (mChromePdfViewerFragment != null && mChromePdfViewerFragment.mIsLoadDocumentSuccess) {
            mChromePdfViewerFragment.setTextSearchActive(true);
            return true;
        }
        return false;
    }

    /**
     * Called after a pdf page has been removed from the view hierarchy and will no longer be used.
     */
    void destroy() {
        mChromePdfViewerFragment = null;
    }

    /**
     * Called after pdf download complete.
     *
     * @param pdfFilePath The filepath of the downloaded pdf document.
     */
    void onDownloadComplete(String pdfFilePath) {
        loadPdfFile(pdfFilePath);
    }

    /** Returns the filepath of the pdf document. */
    String getFilepath() {
        return mPdfFilePath;
    }

    private void loadPdfFile(String pdfFilePath) {
        mPdfFilePath = pdfFilePath;
        loadPdfFile();
    }

    private void loadPdfFile() {
        if (mIsPdfLoaded) {
            return;
        }
        if (mPdfFilePath == null) {
            return;
        }
        if (mView.getParent() == null) {
            return;
        }
        Uri uri = PdfUtils.getUriFromFilePath(mPdfFilePath);
        if (uri != null) {
            try {
                if (!sSkipLoadPdfForTesting) {
                    // Committing the fragment
                    // TODO(b/360717802): Reuse fragment from savedInstance.
                    FragmentTransaction transaction = mFragmentManager.beginTransaction();
                    transaction.add(
                            mChromePdfViewerFragment.mFragmentContainerViewId,
                            mChromePdfViewerFragment);
                    transaction.commitAllowingStateLoss();
                    mFragmentManager.executePendingTransactions();
                    mChromePdfViewerFragment.setDocumentUri(uri);
                }
            } catch (NullPointerException e) {
                Log.e(TAG, "Load pdf fails due to invalid uri.");
            } finally {
                mIsPdfLoaded = true;
            }
        } else {
            // TODO(b/348712628): show some error UI when content URI is null.
            Log.e(TAG, "Uri is null.");
        }
    }

    boolean getIsPdfLoadedForTesting() {
        return mIsPdfLoaded;
    }

    static void skipLoadPdfForTesting(boolean skipLoadPdfForTesting) {
        sSkipLoadPdfForTesting = skipLoadPdfForTesting;
    }
}