// 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;
}
}