org.neotree.export.SummaryPrintDocumentAdapter.java Source code

Java tutorial

Introduction

Here is the source code for org.neotree.export.SummaryPrintDocumentAdapter.java

Source

/*
 * The MIT License (MIT)
 * Copyright (c) 2016 Ubiqueworks Ltd and contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
 * SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
 * OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

package org.neotree.export;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.pdf.PdfDocument;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
import android.print.pdf.PrintedPdfDocument;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;

import org.joda.time.DateTime;
import org.joda.time.format.ISODateTimeFormat;
import org.neotree.NeoTree;
import org.neotree.R;
import org.neotree.diagnosis.DiagnosisException;
import org.neotree.diagnosis.DiagnosisResult;
import org.neotree.diagnosis.Doctor;
import org.neotree.model.firebase.Diagnosis;
import org.neotree.model.realm.Session;
import org.neotree.model.realm.SessionEntry;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * Created by matteo on 22/09/2016.
 */

public class SummaryPrintDocumentAdapter extends PrintDocumentAdapter {

    private static final String TAG = SummaryPrintDocumentAdapter.class.getSimpleName();

    private static final int MILS_IN_INCH = 1000;

    private static final boolean ENABLE_DIAGNOSIS_PRINT = false;

    private Context mContext;
    private Session mSession;
    private List<SessionEntry> mEntries;
    private List<Diagnosis> mDiagnosisList;
    private DiagnosisResult mDiagnosisResult;

    private Context mPrintContext;
    private PrintAttributes mPrintAttributes;
    private PrintDocumentInfo mPrintDocumentInfo;

    private int mDensity;
    private int mRenderPageMarginTop;
    private int mRenderPageMarginBottom;
    private int mRenderPageMarginLeft;
    private int mRenderPageMarginRight;
    private int mRenderPageWidth;
    private int mRenderPageHeight;
    private int mPageCount;

    public SummaryPrintDocumentAdapter(Context context, Session session, List<SessionEntry> entries,
            List<Diagnosis> diagnoses) {
        mContext = context;
        mSession = session;
        mEntries = entries;
        mDiagnosisList = diagnoses;
    }

    @Override
    public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
            CancellationSignal cancellationSignal, LayoutResultCallback callback, Bundle metadata) {
        // If we are already cancelled, don't do any work.
        if (cancellationSignal.isCanceled()) {
            callback.onLayoutCancelled();
            return;
        }

        boolean layoutNeeded = false;

        mDensity = Math.max(newAttributes.getResolution().getHorizontalDpi(),
                newAttributes.getResolution().getVerticalDpi());
        mRenderPageMarginLeft = (int) (mDensity * (float) newAttributes.getMinMargins().getLeftMils()
                / MILS_IN_INCH);
        mRenderPageMarginRight = (int) (mDensity * (float) newAttributes.getMinMargins().getRightMils()
                / MILS_IN_INCH);

        final int contentWidth = (int) (mDensity * (float) newAttributes.getMediaSize().getWidthMils()
                / MILS_IN_INCH) - mRenderPageMarginLeft - mRenderPageMarginRight;

        if (mRenderPageWidth != contentWidth) {
            mRenderPageWidth = contentWidth;
            layoutNeeded = true;
        }

        mRenderPageMarginTop = (int) (mDensity * (float) newAttributes.getMinMargins().getTopMils() / MILS_IN_INCH);
        mRenderPageMarginBottom = (int) (mDensity * (float) newAttributes.getMinMargins().getBottomMils()
                / MILS_IN_INCH);

        final int contentHeight = (int) (mDensity * (float) newAttributes.getMediaSize().getHeightMils()
                / MILS_IN_INCH) - mRenderPageMarginTop - mRenderPageMarginBottom;

        if (mRenderPageHeight != contentHeight) {
            mRenderPageHeight = contentHeight;
            layoutNeeded = true;
        }

        if (mPrintContext == null || mPrintContext.getResources().getConfiguration().densityDpi != mDensity) {
            Configuration configuration = new Configuration();
            configuration.densityDpi = mDensity;
            mPrintContext = mContext.createConfigurationContext(configuration);
            mPrintContext.setTheme(android.R.style.Theme_Holo_Light);
        }

        if (!layoutNeeded) {
            callback.onLayoutFinished(mPrintDocumentInfo, false);
            return;
        }

        final LayoutInflater layoutInflater = LayoutInflater.from(mPrintContext);

        new AsyncTask<Void, Void, PrintDocumentInfo>() {
            @Override
            protected void onPreExecute() {
                // First register for cancellation requests.
                cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
                    @Override
                    public void onCancel() {
                        cancel(true);
                    }
                });
                // Stash the attributes as we will need them for rendering.
                mPrintAttributes = newAttributes;
            }

            @SuppressWarnings("WrongThread")
            @Override
            protected PrintDocumentInfo doInBackground(Void... params) {
                // Run diagnose once and cache the result
                mDiagnosisResult = diagnose();

                final SessionEntryViewAdapter adapter = new SessionEntryViewAdapter(new ArrayList<>(mEntries),
                        mDiagnosisResult, layoutInflater);

                // Stash the attributes as we will need them for rendering.
                mPrintAttributes = newAttributes;

                // Calculate the layout
                try {
                    LinearLayout dummyParent = new LinearLayout(mPrintContext);
                    dummyParent.setOrientation(LinearLayout.VERTICAL);

                    int currentPage = 0;
                    int currentColumn = 0;
                    int columnRenderHeight = 0;
                    int columnContentHeight = 0;
                    boolean isPageOverflow = false;
                    boolean isColumnOverflow = false;

                    View pageView = null;
                    View sectionView;
                    View entryView;

                    LinearLayout columnView = null;
                    ArrayList<LinearLayout> columnViews = null;

                    SessionEntry entry;
                    String lastSectionTitle = null;

                    final int itemCount = adapter.getCount();
                    for (int i = 0; i < itemCount; i++) {
                        // Be nice and respond to cancellation.
                        if (cancellationSignal.isCanceled()) {
                            callback.onLayoutCancelled();
                            return null;
                        }

                        entry = (SessionEntry) adapter.getItem(i);

                        // Create the first page
                        if (pageView == null) {
                            pageView = createPageView(layoutInflater, dummyParent, currentPage);

                            final View leftColumn = pageView.findViewById(R.id.pdf_page_body_left);
                            final View rightColumn = pageView.findViewById(R.id.pdf_page_body_right);

                            columnViews = new ArrayList<>();
                            columnViews.add((LinearLayout) leftColumn);
                            columnViews.add((LinearLayout) rightColumn);

                            currentColumn = 0;

                            columnView = columnViews.get(currentColumn);
                            columnRenderHeight = columnView.getMeasuredHeight() - columnView.getPaddingTop()
                                    - columnView.getPaddingBottom();
                            columnContentHeight = 0;
                        }

                        // Create a new section if required
                        String entrySectionTitle = null;
                        boolean isSectionChanged = false;
                        if (entry != null) {
                            entrySectionTitle = (entry.getSectionTitle() != null) ? entry.getSectionTitle().trim()
                                    : "Generic answer section";
                            isSectionChanged = lastSectionTitle == null
                                    || !lastSectionTitle.equalsIgnoreCase(entrySectionTitle);
                            if (lastSectionTitle == null | isSectionChanged) {
                                lastSectionTitle = entrySectionTitle;
                            }
                        }

                        sectionView = null;
                        if (isSectionChanged) {
                            sectionView = createSectionView(layoutInflater, dummyParent, entrySectionTitle);
                            measureView(sectionView, columnView.getMeasuredWidth(), columnView.getMeasuredHeight());
                        }

                        // Get and measure the next entry view
                        entryView = adapter.getView(i, null, dummyParent);
                        measureView(entryView, columnView.getMeasuredWidth(), columnView.getMeasuredHeight());

                        // Check we can fit the entry in the current column
                        if (sectionView != null) {
                            isColumnOverflow = (columnContentHeight + sectionView.getMeasuredHeight()
                                    + entryView.getMeasuredHeight() > columnRenderHeight);
                        } else {
                            isColumnOverflow = (columnContentHeight
                                    + entryView.getMeasuredHeight() > columnRenderHeight);
                        }

                        // Check if we have a page overflow
                        isPageOverflow = isColumnOverflow && (currentColumn == columnViews.size() - 1);

                        if (isPageOverflow) {
                            // Increment page counter
                            currentPage++;

                            // Move content to the first columnt of the next page
                            pageView = createPageView(layoutInflater, dummyParent, currentPage);

                            final View leftColumn = pageView.findViewById(R.id.pdf_page_body_left);
                            final View rightColumn = pageView.findViewById(R.id.pdf_page_body_right);

                            columnViews = new ArrayList<>();
                            columnViews.add((LinearLayout) leftColumn);
                            columnViews.add((LinearLayout) rightColumn);

                            currentColumn = 0;

                            columnView = columnViews.get(currentColumn);
                            columnRenderHeight = columnView.getMeasuredHeight() - columnView.getPaddingTop()
                                    - columnView.getPaddingBottom();
                            columnContentHeight = 0;
                            if (sectionView != null) {
                                columnContentHeight += sectionView.getMeasuredHeight();
                            }
                            columnContentHeight += entryView.getMeasuredHeight();
                        } else {
                            if (isColumnOverflow) {
                                // Move content to the next column
                                currentColumn++;

                                columnView = columnViews.get(currentColumn);
                                columnRenderHeight = columnView.getMeasuredHeight() - columnView.getPaddingTop()
                                        - columnView.getPaddingBottom();
                                columnContentHeight = 0;
                                if (sectionView != null) {
                                    columnContentHeight += sectionView.getMeasuredHeight();
                                }
                                columnContentHeight += entryView.getMeasuredHeight();
                            } else {
                                // Add content to the current column
                                if (sectionView != null) {
                                    columnContentHeight += sectionView.getMeasuredHeight();
                                }
                                columnContentHeight += entryView.getMeasuredHeight();
                            }
                        }
                    }

                    mPageCount = currentPage + 1;

                    Log.d(TAG, String.format("Pdf layout completed. Total pages [count=%d]", mPageCount));

                    // Create a document info describing the result.
                    PrintDocumentInfo info = new PrintDocumentInfo.Builder(
                            String.format(Locale.getDefault(), "%s.pdf", mSession.getSessionId()))
                                    .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
                                    .setPageCount(mPageCount).build();

                    // Notify of layout completed
                    callback.onLayoutFinished(info, true);

                    // Update the print document info
                    return info;
                } catch (Exception e) {
                    callback.onLayoutFailed(null);
                    throw new RuntimeException(e);
                }
            }

            @Override
            protected void onPostExecute(PrintDocumentInfo result) {
                mPrintDocumentInfo = result;
            }

            @Override
            protected void onCancelled(PrintDocumentInfo result) {
                callback.onLayoutCancelled();
            }
        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
    }

    @Override
    public void onWrite(PageRange[] pages, ParcelFileDescriptor destination, CancellationSignal cancellationSignal,
            PrintDocumentAdapter.WriteResultCallback callback) {
        // If we are already cancelled, don't do any work.
        if (cancellationSignal.isCanceled()) {
            callback.onWriteCancelled();
            return;
        }

        new AsyncTask<Void, Void, Void>() {

            final SparseIntArray mWrittenPages = new SparseIntArray();
            final PrintedPdfDocument mPdfDocument = new PrintedPdfDocument(mPrintContext, mPrintAttributes);

            @Override
            protected void onPreExecute() {
                // First register for cancellation requests.
                cancellationSignal.setOnCancelListener(() -> cancel(true));
            }

            @SuppressWarnings("WrongThread")
            @Override
            protected Void doInBackground(Void... params) {
                // The content is laid out and rendered in screen pixels with the width and height of the
                // paper size times the print density but the PDF canvas size is in points which are 1/72",
                // so we will scale down the content.
                final float scale = Math.min((float) mPdfDocument.getPageContentRect().width() / mRenderPageWidth,
                        (float) mPdfDocument.getPageContentRect().height() / mRenderPageHeight);

                final LayoutInflater layoutInflater = LayoutInflater.from(mPrintContext);
                final SessionEntryViewAdapter adapter = new SessionEntryViewAdapter(mEntries, mDiagnosisResult,
                        layoutInflater);

                PdfDocument.Page pdfPage = null;
                ArrayList<View> pageViews = new ArrayList<>();

                LinearLayout dummyParent = new LinearLayout(mPrintContext);
                dummyParent.setOrientation(LinearLayout.VERTICAL);

                int currentPage = 0;
                int currentColumn = 0;
                int columnRenderHeight = 0;
                int columnContentHeight = 0;
                boolean isPageOverflow = false;
                boolean isColumnOverflow = false;

                View pageView = null;
                View sectionView = null;
                View entryView = null;

                LinearLayout columnView = null;
                ArrayList<LinearLayout> columnViews = null;

                SessionEntry entry;
                String lastSectionTitle = null;

                final int itemCount = adapter.getCount();
                Log.d(TAG, String.format("Total items [count=%d]", itemCount));

                for (int i = 0; i < itemCount; i++) {
                    // Be nice and respond to cancellation.
                    if (cancellationSignal.isCanceled()) {
                        callback.onWriteCancelled();
                        return null;
                    }

                    final boolean isFirstItem = (i == 0);
                    entry = (SessionEntry) adapter.getItem(i);

                    // Create the first page
                    if (pageView == null) {
                        pageView = createPageView(layoutInflater, dummyParent, currentPage);

                        final View leftColumn = pageView.findViewById(R.id.pdf_page_body_left);
                        final View rightColumn = pageView.findViewById(R.id.pdf_page_body_right);

                        columnViews = new ArrayList<>();
                        columnViews.add((LinearLayout) leftColumn);
                        columnViews.add((LinearLayout) rightColumn);

                        currentColumn = 0;

                        columnView = columnViews.get(currentColumn);
                        columnRenderHeight = columnView.getMeasuredHeight() - columnView.getPaddingTop()
                                - columnView.getPaddingBottom();
                        columnContentHeight = 0;
                    }

                    // Create a new section if required
                    String entrySectionTitle = null;
                    boolean isSectionChanged = false;
                    if (entry != null) {
                        entrySectionTitle = (entry.getSectionTitle() != null) ? entry.getSectionTitle().trim()
                                : "Generic answer section";
                        isSectionChanged = lastSectionTitle == null
                                || !lastSectionTitle.equalsIgnoreCase(entrySectionTitle);
                        if (lastSectionTitle == null | isSectionChanged) {
                            lastSectionTitle = entrySectionTitle;
                        }
                    }

                    sectionView = null;
                    if (isSectionChanged) {
                        sectionView = createSectionView(layoutInflater, dummyParent, entrySectionTitle);
                        measureView(sectionView, columnView.getMeasuredWidth(), columnView.getMeasuredHeight());
                    }

                    // Get and measure the next entry view
                    entryView = adapter.getView(i, null, dummyParent);
                    measureView(entryView, columnView.getMeasuredWidth(), columnView.getMeasuredHeight());

                    // Check we can fit the entry in the current column
                    if (sectionView != null) {
                        isColumnOverflow = (columnContentHeight + sectionView.getMeasuredHeight()
                                + entryView.getMeasuredHeight() > columnRenderHeight);
                    } else {
                        isColumnOverflow = (columnContentHeight
                                + entryView.getMeasuredHeight() > columnRenderHeight);
                    }

                    // Check if we have a page overflow
                    isPageOverflow = isColumnOverflow && (currentColumn == columnViews.size() - 1);

                    if (isPageOverflow) {
                        currentPage++;

                        // Done with the current page - finish it.
                        if (pdfPage != null) {
                            Log.d(TAG, String.format("Finish pdf page at item [i=%d]", i));
                            finishCurrentPage(pageView, pageViews, pdfPage);
                            mPdfDocument.finishPage(pdfPage);
                        }

                        // Move content to the first columnt of the next page
                        pageView = createPageView(layoutInflater, dummyParent, currentPage);

                        final View leftColumn = pageView.findViewById(R.id.pdf_page_body_left);
                        final View rightColumn = pageView.findViewById(R.id.pdf_page_body_right);

                        columnViews = new ArrayList<>();
                        columnViews.add((LinearLayout) leftColumn);
                        columnViews.add((LinearLayout) rightColumn);

                        currentColumn = 0;

                        columnView = columnViews.get(currentColumn);
                        columnRenderHeight = columnView.getMeasuredHeight() - columnView.getPaddingTop()
                                - columnView.getPaddingBottom();
                        columnContentHeight = 0;
                        if (sectionView != null) {
                            sectionView.layout(columnView.getLeft(), columnContentHeight,
                                    columnView.getLeft() + sectionView.getMeasuredWidth(),
                                    columnContentHeight + sectionView.getMeasuredHeight());
                            columnContentHeight += sectionView.getMeasuredHeight();

                            if (pdfPage != null) {
                                pageViews.add(sectionView);
                            }
                        }
                        entryView.layout(columnView.getLeft(), columnContentHeight,
                                columnView.getLeft() + entryView.getMeasuredWidth(),
                                columnContentHeight + entryView.getMeasuredHeight());
                        columnContentHeight += entryView.getMeasuredHeight();

                    } else {
                        if (isColumnOverflow) {
                            // Move content to the next column
                            currentColumn++;

                            columnView = columnViews.get(currentColumn);
                            //columnRenderHeight = columnView.getMeasuredHeight() - columnView.getPaddingTop() - columnView.getPaddingBottom();
                            columnContentHeight = 0;
                            if (sectionView != null) {
                                sectionView.layout(columnView.getLeft(), columnContentHeight,
                                        columnView.getLeft() + sectionView.getMeasuredWidth(),
                                        columnContentHeight + sectionView.getMeasuredHeight());
                                columnContentHeight += sectionView.getMeasuredHeight();

                                if (pdfPage != null) {
                                    pageViews.add(sectionView);
                                }
                            }
                            entryView.layout(columnView.getLeft(), columnContentHeight,
                                    columnView.getLeft() + entryView.getMeasuredWidth(),
                                    columnContentHeight + entryView.getMeasuredHeight());
                            columnContentHeight += entryView.getMeasuredHeight();
                        } else {
                            // Add content to the current column
                            if (sectionView != null) {
                                sectionView.layout(columnView.getLeft(), columnContentHeight,
                                        columnView.getLeft() + sectionView.getMeasuredWidth(),
                                        columnContentHeight + sectionView.getMeasuredHeight());
                                columnContentHeight += sectionView.getMeasuredHeight();

                                if (isFirstItem || pdfPage != null) {
                                    pageViews.add(sectionView);
                                }
                            }
                            entryView.layout(columnView.getLeft(), columnContentHeight,
                                    columnView.getLeft() + entryView.getMeasuredWidth(),
                                    columnContentHeight + entryView.getMeasuredHeight());
                            columnContentHeight += entryView.getMeasuredHeight();
                        }
                    }

                    // If the page is requested, create the page for rendering.
                    if (isFirstItem || isPageOverflow) {
                        if (containsPage(pages, currentPage)) {
                            Log.d(TAG, String.format("Create pdf page at item [i=%d]", i));
                            pdfPage = mPdfDocument.startPage(currentPage);
                            pdfPage.getCanvas().scale(scale, scale);

                            // Render the pageView
                            pageView.layout(mRenderPageMarginLeft, mRenderPageMarginTop,
                                    pageView.getMeasuredWidth(), pageView.getMeasuredHeight());
                            pageView.draw(pdfPage.getCanvas());

                            // Keep track which pages are written.
                            mWrittenPages.append(mWrittenPages.size(), currentPage);

                            if (currentPage > 0) {
                                pageViews = new ArrayList<>();
                            }
                        } else {
                            pdfPage = null;
                            pageViews = new ArrayList<>();
                        }
                    }

                    // If the current view is on a requested page, add it to the render queue.
                    if (pdfPage != null) {
                        pageViews.add(entryView);
                    }
                }

                // Done with the last page.
                if (pdfPage != null) {
                    Log.d(TAG, "Finish last page");
                    finishCurrentPage(pageView, pageViews, pdfPage);
                    mPdfDocument.finishPage(pdfPage);
                }

                // Be nice and respond to cancellation.
                if (cancellationSignal.isCanceled()) {
                    callback.onWriteCancelled();
                    return null;
                }

                // Write the data and return success or failure.
                try {
                    mPdfDocument.writeTo(new FileOutputStream(destination.getFileDescriptor()));
                    // Compute which page ranges were written based on the bookkeeping we maintained.
                    PageRange[] pageRanges = computeWrittenPageRanges(mWrittenPages);
                    callback.onWriteFinished(pageRanges);
                } catch (IOException ioe) {
                    callback.onWriteFailed(null);
                } finally {
                    mPdfDocument.close();
                }

                return null;
            }

            @Override
            protected void onCancelled(Void result) {
                // Task was cancelled, report that.
                callback.onWriteCancelled();
                mPdfDocument.close();
            }

        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
    }

    private LinearLayout createPageView(LayoutInflater layoutInflater, ViewGroup parent, int currentPage) {
        LinearLayout pageView = (LinearLayout) layoutInflater.inflate(R.layout.view_pdf_page, parent, false);

        if (currentPage > 0) {
            pageView.findViewById(R.id.pdf_cover_title).setVisibility(View.GONE);
        }
        ((TextView) pageView.findViewById(R.id.pdf_page_session_id)).setText(mSession.getSessionId());
        ((TextView) pageView.findViewById(R.id.pdf_page_creation_date))
                .setText(formatDateTime(mPrintContext, mSession.getCreatedAt()));
        ((TextView) pageView.findViewById(R.id.pdf_page_number))
                .setText(mPrintContext.getString(R.string.pdf_label_page, currentPage + 1, mPageCount));

        final int widthMeasureSpec = ViewGroup.getChildMeasureSpec(
                View.MeasureSpec.makeMeasureSpec(mRenderPageWidth, View.MeasureSpec.EXACTLY), 0,
                pageView.getLayoutParams().width);
        final int heightMeasureSpec = ViewGroup.getChildMeasureSpec(
                View.MeasureSpec.makeMeasureSpec(mRenderPageHeight, View.MeasureSpec.EXACTLY), 0,
                pageView.getLayoutParams().height);
        pageView.measure(widthMeasureSpec, heightMeasureSpec);
        pageView.layout(0, 0, mRenderPageWidth, mRenderPageHeight);

        return pageView;
    }

    private View createSectionView(LayoutInflater layoutInflater, ViewGroup parent, String title) {
        View sectionView = layoutInflater.inflate(R.layout.view_pdf_section, parent, false);
        ((TextView) sectionView.findViewById(R.id.pdf_section_title)).setText(title);
        return sectionView;
    }

    private DiagnosisResult diagnose() {
        Map<String, Object> entriesMap = null;
        for (SessionEntry entry : mEntries) {
            if (entriesMap == null) {
                entriesMap = new HashMap<>();
            }
            entriesMap.put(entry.getKey(), entry.getValue());
        }

        try {
            return Doctor.diagnose(mDiagnosisList, entriesMap);
        } catch (DiagnosisException e) {
            Log.e(TAG, "Error running diagnosis", e);
        }
        return null;
    }

    private void measureView(View view, int parentWidth, int parentHeight) {
        final int widthMeasureSpec = ViewGroup.getChildMeasureSpec(
                View.MeasureSpec.makeMeasureSpec(parentWidth, View.MeasureSpec.EXACTLY), 0,
                view.getLayoutParams().width);
        final int heightMeasureSpec = ViewGroup.getChildMeasureSpec(
                View.MeasureSpec.makeMeasureSpec(parentHeight, View.MeasureSpec.UNSPECIFIED), 0,
                view.getLayoutParams().height);
        view.measure(widthMeasureSpec, heightMeasureSpec);
    }

    private void finishCurrentPage(View pageView, ArrayList<View> bodyViews, PdfDocument.Page pdfPage) {
        final View bodyContainerView = pageView.findViewById(R.id.pdf_page_body_container);

        // Render all entries for the current page
        int bodyLeft = pageView.getPaddingLeft() + pageView.getLeft();
        int bodyTop = bodyContainerView.getTop() + pageView.getPaddingTop();

        Canvas pdfCanvas = pdfPage.getCanvas();
        pdfCanvas.translate(bodyLeft, bodyTop);

        for (View childView : bodyViews) {
            pdfCanvas.save();
            pdfCanvas.translate(childView.getLeft(), childView.getTop());
            childView.draw(pdfPage.getCanvas());
            pdfCanvas.restore();
        }
    }

    @SuppressLint("IntRange")
    private PageRange[] computeWrittenPageRanges(SparseIntArray writtenPages) {
        List<PageRange> pageRanges = new ArrayList<>();
        int start = -1;
        int end = -1;
        final int writtenPageCount = writtenPages.size();
        for (int i = 0; i < writtenPageCount; i++) {
            if (start < 0) {
                start = writtenPages.valueAt(i);
            }

            int oldEnd = end = start;
            while (i < writtenPageCount && (end - oldEnd) <= 1) {
                oldEnd = end;
                end = writtenPages.valueAt(i);
                i++;
            }

            //noinspection Range
            PageRange pageRange = new PageRange(start, end);
            pageRanges.add(pageRange);
            start = end = -1;
        }
        PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
        pageRanges.toArray(pageRangesArray);
        return pageRangesArray;
    }

    private boolean containsPage(PageRange[] pageRanges, int page) {
        final int pageRangeCount = pageRanges.length;
        for (int i = 0; i < pageRangeCount; i++) {
            if (pageRanges[i].getStart() <= page && pageRanges[i].getEnd() >= page) {
                return true;
            }
        }
        return false;
    }

    private String formatDateTime(Context context, String value) {
        return (TextUtils.isEmpty(value)) ? context.getString(R.string.label_not_set)
                : DateTime.parse(value, ISODateTimeFormat.dateTimeNoMillis())
                        .toString(NeoTree.NeoTreeFormat.DATETIME);
    }

    private class SessionEntryViewAdapter extends BaseAdapter {

        private static final int VIEW_TYPE_ENTRY = 100;
        private static final int VIEW_TYPE_DIAGNOSIS = 101;

        private final List<SessionEntry> mItems;
        private final LayoutInflater mInflater;
        private final DiagnosisResult mDiagnosisResult;

        public SessionEntryViewAdapter(List<SessionEntry> items, DiagnosisResult diagnosisResult,
                LayoutInflater inflater) {
            mItems = items;
            mDiagnosisResult = diagnosisResult;
            mInflater = inflater;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            int viewType = getItemViewType(position);
            switch (viewType) {
            case VIEW_TYPE_ENTRY:
                if (convertView == null) {
                    convertView = mInflater.inflate(R.layout.view_pdf_entry, parent, false);
                }

                SessionEntry entry = (SessionEntry) getItem(position);

                TextView textEntryLabel = (TextView) convertView.findViewById(R.id.pdf_entry_label);
                textEntryLabel.setText(entry.getLabel());

                TextView textEntryValue = (TextView) convertView.findViewById(R.id.pdf_entry_value);
                textEntryValue.setText(entry.getValueAsString(mPrintContext));
                break;

            case VIEW_TYPE_DIAGNOSIS:
                if (convertView == null) {
                    convertView = mInflater.inflate(R.layout.view_pdf_diagnosis_section, parent, false);
                }

                LinearLayout diagnosesContainer = (LinearLayout) convertView
                        .findViewById(R.id.pdf_diagnosis_container);
                LinearLayout managementContainer = (LinearLayout) convertView
                        .findViewById(R.id.pdf_management_container);

                if (mDiagnosisResult.getDiagnosisCount() > 0) {
                    for (int i = 0; i < mDiagnosisResult.getDiagnosisCount(); i++) {
                        View view = mInflater.inflate(R.layout.view_pdf_diagnosis_entry, diagnosesContainer, false);
                        ((TextView) view.findViewById(R.id.pdf_entry_label))
                                .setText(mDiagnosisResult.getDiagnosisName(i));
                        diagnosesContainer.addView(view);
                    }
                } else {
                    convertView.findViewById(R.id.pdf_diagnosis_title).setVisibility(View.GONE);
                }

                if (mDiagnosisResult.getManagementCount() > 0) {
                    for (int i = 0; i < mDiagnosisResult.getManagementCount(); i++) {
                        View view = mInflater.inflate(R.layout.view_pdf_diagnosis_entry, managementContainer,
                                false);
                        ((TextView) view.findViewById(R.id.pdf_entry_label))
                                .setText(mDiagnosisResult.getDiagnosisManagement(i).text);
                        managementContainer.addView(view);
                    }
                } else {
                    convertView.findViewById(R.id.pdf_management_title).setVisibility(View.GONE);
                }
                break;
            }
            return convertView;
        }

        @Override
        public int getCount() {
            return ((mItems != null) ? mItems.size() : 0) + ((ENABLE_DIAGNOSIS_PRINT) ? 1 : 0);
        }

        @Override
        public Object getItem(int position) {
            if (ENABLE_DIAGNOSIS_PRINT) {
                if (position == getCount() - 1) {
                    // Last position is a fake entry to display the diagnosis section
                    return null;
                }
            }
            return (mItems != null) ? mItems.get(position) : null;
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public int getItemViewType(int position) {
            if (ENABLE_DIAGNOSIS_PRINT) {
                if (position == getCount() - 1) {
                    // Last position is a fake entry to display the diagnosis section
                    return VIEW_TYPE_DIAGNOSIS;
                }
            }
            return VIEW_TYPE_ENTRY;
        }

    }

}