13bf66744d61d18c66d46f2608de0467ad3df0268Mopria/*
23bf66744d61d18c66d46f2608de0467ad3df0268Mopria * Copyright (C) 2016 The Android Open Source Project
33bf66744d61d18c66d46f2608de0467ad3df0268Mopria * Copyright (C) 2016 Mopria Alliance, Inc.
43bf66744d61d18c66d46f2608de0467ad3df0268Mopria *
53bf66744d61d18c66d46f2608de0467ad3df0268Mopria * Licensed under the Apache License, Version 2.0 (the "License");
63bf66744d61d18c66d46f2608de0467ad3df0268Mopria * you may not use this file except in compliance with the License.
73bf66744d61d18c66d46f2608de0467ad3df0268Mopria * You may obtain a copy of the License at
83bf66744d61d18c66d46f2608de0467ad3df0268Mopria *
93bf66744d61d18c66d46f2608de0467ad3df0268Mopria *      http://www.apache.org/licenses/LICENSE-2.0
103bf66744d61d18c66d46f2608de0467ad3df0268Mopria *
113bf66744d61d18c66d46f2608de0467ad3df0268Mopria * Unless required by applicable law or agreed to in writing, software
123bf66744d61d18c66d46f2608de0467ad3df0268Mopria * distributed under the License is distributed on an "AS IS" BASIS,
133bf66744d61d18c66d46f2608de0467ad3df0268Mopria * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
143bf66744d61d18c66d46f2608de0467ad3df0268Mopria * See the License for the specific language governing permissions and
153bf66744d61d18c66d46f2608de0467ad3df0268Mopria * limitations under the License.
163bf66744d61d18c66d46f2608de0467ad3df0268Mopria */
173bf66744d61d18c66d46f2608de0467ad3df0268Mopria
183bf66744d61d18c66d46f2608de0467ad3df0268Mopriapackage com.android.bips.render;
193bf66744d61d18c66d46f2608de0467ad3df0268Mopria
203bf66744d61d18c66d46f2608de0467ad3df0268Mopriaimport android.app.Service;
213bf66744d61d18c66d46f2608de0467ad3df0268Mopriaimport android.content.Intent;
223bf66744d61d18c66d46f2608de0467ad3df0268Mopriaimport android.graphics.Bitmap;
233bf66744d61d18c66d46f2608de0467ad3df0268Mopriaimport android.graphics.Matrix;
243bf66744d61d18c66d46f2608de0467ad3df0268Mopriaimport android.graphics.pdf.PdfRenderer;
253bf66744d61d18c66d46f2608de0467ad3df0268Mopriaimport android.os.IBinder;
263bf66744d61d18c66d46f2608de0467ad3df0268Mopriaimport android.os.ParcelFileDescriptor;
273bf66744d61d18c66d46f2608de0467ad3df0268Mopriaimport android.os.RemoteException;
283bf66744d61d18c66d46f2608de0467ad3df0268Mopriaimport android.util.Log;
293bf66744d61d18c66d46f2608de0467ad3df0268Mopria
303bf66744d61d18c66d46f2608de0467ad3df0268Mopriaimport com.android.bips.jni.SizeD;
313bf66744d61d18c66d46f2608de0467ad3df0268Mopria
323bf66744d61d18c66d46f2608de0467ad3df0268Mopriaimport java.io.IOException;
333bf66744d61d18c66d46f2608de0467ad3df0268Mopriaimport java.io.OutputStream;
343bf66744d61d18c66d46f2608de0467ad3df0268Mopriaimport java.nio.ByteBuffer;
353bf66744d61d18c66d46f2608de0467ad3df0268Mopria
363bf66744d61d18c66d46f2608de0467ad3df0268Mopria/**
373bf66744d61d18c66d46f2608de0467ad3df0268Mopria * Implements a PDF rendering service which can be run in an isolated process
383bf66744d61d18c66d46f2608de0467ad3df0268Mopria */
393bf66744d61d18c66d46f2608de0467ad3df0268Mopriapublic class PdfRenderService extends Service {
403bf66744d61d18c66d46f2608de0467ad3df0268Mopria    private static final String TAG = PdfRenderService.class.getSimpleName();
413bf66744d61d18c66d46f2608de0467ad3df0268Mopria    private static final boolean DEBUG = false;
423bf66744d61d18c66d46f2608de0467ad3df0268Mopria
433bf66744d61d18c66d46f2608de0467ad3df0268Mopria    /** How large of a chunk of Bitmap data to copy at once to the output stream */
443bf66744d61d18c66d46f2608de0467ad3df0268Mopria    private static final int MAX_BYTES_PER_CHUNK = 1024 * 1024 * 5;
453bf66744d61d18c66d46f2608de0467ad3df0268Mopria
463bf66744d61d18c66d46f2608de0467ad3df0268Mopria    private PdfRenderer mRenderer;
473bf66744d61d18c66d46f2608de0467ad3df0268Mopria    private PdfRenderer.Page mPage;
483bf66744d61d18c66d46f2608de0467ad3df0268Mopria
493bf66744d61d18c66d46f2608de0467ad3df0268Mopria    /** Lock held to protect against close() of current page during rendering. */
503bf66744d61d18c66d46f2608de0467ad3df0268Mopria    private final Object mPageOpenLock = new Object();
513bf66744d61d18c66d46f2608de0467ad3df0268Mopria
523bf66744d61d18c66d46f2608de0467ad3df0268Mopria    @Override
533bf66744d61d18c66d46f2608de0467ad3df0268Mopria    public IBinder onBind(Intent intent) {
543bf66744d61d18c66d46f2608de0467ad3df0268Mopria        return mBinder;
553bf66744d61d18c66d46f2608de0467ad3df0268Mopria    }
563bf66744d61d18c66d46f2608de0467ad3df0268Mopria
573bf66744d61d18c66d46f2608de0467ad3df0268Mopria    @Override
583bf66744d61d18c66d46f2608de0467ad3df0268Mopria    public boolean onUnbind(Intent intent) {
593bf66744d61d18c66d46f2608de0467ad3df0268Mopria        closeAll();
603bf66744d61d18c66d46f2608de0467ad3df0268Mopria        return super.onUnbind(intent);
613bf66744d61d18c66d46f2608de0467ad3df0268Mopria    }
623bf66744d61d18c66d46f2608de0467ad3df0268Mopria
633bf66744d61d18c66d46f2608de0467ad3df0268Mopria    private final IPdfRender.Stub mBinder = new IPdfRender.Stub() {
643bf66744d61d18c66d46f2608de0467ad3df0268Mopria        @Override
653bf66744d61d18c66d46f2608de0467ad3df0268Mopria        public int openDocument(ParcelFileDescriptor pfd) throws RemoteException {
663bf66744d61d18c66d46f2608de0467ad3df0268Mopria            if (!open(pfd)) return 0;
673bf66744d61d18c66d46f2608de0467ad3df0268Mopria            return mRenderer.getPageCount();
683bf66744d61d18c66d46f2608de0467ad3df0268Mopria        }
693bf66744d61d18c66d46f2608de0467ad3df0268Mopria
703bf66744d61d18c66d46f2608de0467ad3df0268Mopria        @Override
713bf66744d61d18c66d46f2608de0467ad3df0268Mopria        public SizeD getPageSize(int page) throws RemoteException {
723bf66744d61d18c66d46f2608de0467ad3df0268Mopria            if (!openPage(page)) return null;
733bf66744d61d18c66d46f2608de0467ad3df0268Mopria            return new SizeD(mPage.getWidth(), mPage.getHeight());
743bf66744d61d18c66d46f2608de0467ad3df0268Mopria        }
753bf66744d61d18c66d46f2608de0467ad3df0268Mopria
763bf66744d61d18c66d46f2608de0467ad3df0268Mopria        @Override
773bf66744d61d18c66d46f2608de0467ad3df0268Mopria        public ParcelFileDescriptor renderPageStripe(int page, int y, int width, int height,
783bf66744d61d18c66d46f2608de0467ad3df0268Mopria                double zoomFactor)
793bf66744d61d18c66d46f2608de0467ad3df0268Mopria                throws RemoteException {
803bf66744d61d18c66d46f2608de0467ad3df0268Mopria            if (!openPage(page)) return null;
813bf66744d61d18c66d46f2608de0467ad3df0268Mopria
823bf66744d61d18c66d46f2608de0467ad3df0268Mopria            // Create a pipe with input and output sides
833bf66744d61d18c66d46f2608de0467ad3df0268Mopria            ParcelFileDescriptor pipes[];
843bf66744d61d18c66d46f2608de0467ad3df0268Mopria            try {
853bf66744d61d18c66d46f2608de0467ad3df0268Mopria                pipes = ParcelFileDescriptor.createPipe();
863bf66744d61d18c66d46f2608de0467ad3df0268Mopria            } catch (IOException e) {
873bf66744d61d18c66d46f2608de0467ad3df0268Mopria                return null;
883bf66744d61d18c66d46f2608de0467ad3df0268Mopria            }
893bf66744d61d18c66d46f2608de0467ad3df0268Mopria
903bf66744d61d18c66d46f2608de0467ad3df0268Mopria            // Use a thread to spool out the bitmap data
913bf66744d61d18c66d46f2608de0467ad3df0268Mopria            new RenderThread(mPage, y, width, height, zoomFactor, pipes[1]).start();
923bf66744d61d18c66d46f2608de0467ad3df0268Mopria
933bf66744d61d18c66d46f2608de0467ad3df0268Mopria            // Return the corresponding input stream.
943bf66744d61d18c66d46f2608de0467ad3df0268Mopria            return pipes[0];
953bf66744d61d18c66d46f2608de0467ad3df0268Mopria        }
963bf66744d61d18c66d46f2608de0467ad3df0268Mopria
973bf66744d61d18c66d46f2608de0467ad3df0268Mopria        @Override
983bf66744d61d18c66d46f2608de0467ad3df0268Mopria        public void closeDocument() throws RemoteException {
993bf66744d61d18c66d46f2608de0467ad3df0268Mopria            if (DEBUG) Log.d(TAG, "closeDocument");
1003bf66744d61d18c66d46f2608de0467ad3df0268Mopria            closeAll();
1013bf66744d61d18c66d46f2608de0467ad3df0268Mopria        }
1023bf66744d61d18c66d46f2608de0467ad3df0268Mopria
1033bf66744d61d18c66d46f2608de0467ad3df0268Mopria        /**
1043bf66744d61d18c66d46f2608de0467ad3df0268Mopria         * Ensure the specified PDF file is open, closing the old file if necessary, and returning
1053bf66744d61d18c66d46f2608de0467ad3df0268Mopria         * true if successful.
1063bf66744d61d18c66d46f2608de0467ad3df0268Mopria         */
1073bf66744d61d18c66d46f2608de0467ad3df0268Mopria        private boolean open(ParcelFileDescriptor pfd) {
1083bf66744d61d18c66d46f2608de0467ad3df0268Mopria            closeAll();
1093bf66744d61d18c66d46f2608de0467ad3df0268Mopria
1103bf66744d61d18c66d46f2608de0467ad3df0268Mopria            try {
1113bf66744d61d18c66d46f2608de0467ad3df0268Mopria                mRenderer = new PdfRenderer(pfd);
1123bf66744d61d18c66d46f2608de0467ad3df0268Mopria            } catch (IOException e) {
1133bf66744d61d18c66d46f2608de0467ad3df0268Mopria                Log.w(TAG, "Could not open file descriptor for rendering", e);
1143bf66744d61d18c66d46f2608de0467ad3df0268Mopria                return false;
1153bf66744d61d18c66d46f2608de0467ad3df0268Mopria            }
1163bf66744d61d18c66d46f2608de0467ad3df0268Mopria            return true;
1173bf66744d61d18c66d46f2608de0467ad3df0268Mopria        }
1183bf66744d61d18c66d46f2608de0467ad3df0268Mopria
1193bf66744d61d18c66d46f2608de0467ad3df0268Mopria        /**
1203bf66744d61d18c66d46f2608de0467ad3df0268Mopria         * Ensure the specified PDF file and page are open, closing the old file if necessary, and
1213bf66744d61d18c66d46f2608de0467ad3df0268Mopria         * returning true if successful.
1223bf66744d61d18c66d46f2608de0467ad3df0268Mopria         */
1233bf66744d61d18c66d46f2608de0467ad3df0268Mopria        private boolean openPage(int page) {
1243bf66744d61d18c66d46f2608de0467ad3df0268Mopria            if (mRenderer == null) return false;
1253bf66744d61d18c66d46f2608de0467ad3df0268Mopria
1263bf66744d61d18c66d46f2608de0467ad3df0268Mopria            // Close old page if this is a new page
1273bf66744d61d18c66d46f2608de0467ad3df0268Mopria            if (mPage != null && mPage.getIndex() != page) {
1283bf66744d61d18c66d46f2608de0467ad3df0268Mopria                closePage();
1293bf66744d61d18c66d46f2608de0467ad3df0268Mopria            }
1303bf66744d61d18c66d46f2608de0467ad3df0268Mopria
1313bf66744d61d18c66d46f2608de0467ad3df0268Mopria            // Open new page if necessary
1323bf66744d61d18c66d46f2608de0467ad3df0268Mopria            if (mPage == null) {
1333bf66744d61d18c66d46f2608de0467ad3df0268Mopria                mPage = mRenderer.openPage(page);
1343bf66744d61d18c66d46f2608de0467ad3df0268Mopria            }
1353bf66744d61d18c66d46f2608de0467ad3df0268Mopria            return true;
1363bf66744d61d18c66d46f2608de0467ad3df0268Mopria        }
1373bf66744d61d18c66d46f2608de0467ad3df0268Mopria    };
1383bf66744d61d18c66d46f2608de0467ad3df0268Mopria
1393bf66744d61d18c66d46f2608de0467ad3df0268Mopria    /** Close the current page if one is open */
1403bf66744d61d18c66d46f2608de0467ad3df0268Mopria    private void closePage() {
1413bf66744d61d18c66d46f2608de0467ad3df0268Mopria        if (mPage != null) {
1423bf66744d61d18c66d46f2608de0467ad3df0268Mopria            synchronized (mPageOpenLock) {
1433bf66744d61d18c66d46f2608de0467ad3df0268Mopria                mPage.close();
1443bf66744d61d18c66d46f2608de0467ad3df0268Mopria            }
1453bf66744d61d18c66d46f2608de0467ad3df0268Mopria            mPage = null;
1463bf66744d61d18c66d46f2608de0467ad3df0268Mopria        }
1473bf66744d61d18c66d46f2608de0467ad3df0268Mopria    }
1483bf66744d61d18c66d46f2608de0467ad3df0268Mopria
1493bf66744d61d18c66d46f2608de0467ad3df0268Mopria    /**
1503bf66744d61d18c66d46f2608de0467ad3df0268Mopria     * Close the current page and file if open
1513bf66744d61d18c66d46f2608de0467ad3df0268Mopria     */
1523bf66744d61d18c66d46f2608de0467ad3df0268Mopria    private void closeAll() {
1533bf66744d61d18c66d46f2608de0467ad3df0268Mopria        closePage();
1543bf66744d61d18c66d46f2608de0467ad3df0268Mopria
1553bf66744d61d18c66d46f2608de0467ad3df0268Mopria        if (mRenderer != null) {
1563bf66744d61d18c66d46f2608de0467ad3df0268Mopria            mRenderer.close();
1573bf66744d61d18c66d46f2608de0467ad3df0268Mopria            mRenderer = null;
1583bf66744d61d18c66d46f2608de0467ad3df0268Mopria        }
1593bf66744d61d18c66d46f2608de0467ad3df0268Mopria    }
1603bf66744d61d18c66d46f2608de0467ad3df0268Mopria
1613bf66744d61d18c66d46f2608de0467ad3df0268Mopria    /**
1623bf66744d61d18c66d46f2608de0467ad3df0268Mopria     * Renders page data to RGB bytes and writes them to an output stream
1633bf66744d61d18c66d46f2608de0467ad3df0268Mopria     */
1643bf66744d61d18c66d46f2608de0467ad3df0268Mopria    private class RenderThread extends Thread {
1653bf66744d61d18c66d46f2608de0467ad3df0268Mopria        private final PdfRenderer.Page mPage;
1663bf66744d61d18c66d46f2608de0467ad3df0268Mopria        private final int mWidth;
1673bf66744d61d18c66d46f2608de0467ad3df0268Mopria        private final int mYOffset;
1683bf66744d61d18c66d46f2608de0467ad3df0268Mopria        private final int mHeight;
1693bf66744d61d18c66d46f2608de0467ad3df0268Mopria        private final double mZoomFactor;
1703bf66744d61d18c66d46f2608de0467ad3df0268Mopria        private final int mRowsPerStripe;
1713bf66744d61d18c66d46f2608de0467ad3df0268Mopria        private final ParcelFileDescriptor mOutput;
1723bf66744d61d18c66d46f2608de0467ad3df0268Mopria        private final ByteBuffer mBuffer;
1733bf66744d61d18c66d46f2608de0467ad3df0268Mopria
1743bf66744d61d18c66d46f2608de0467ad3df0268Mopria        RenderThread(PdfRenderer.Page page, int y, int width, int height, double zoom,
1753bf66744d61d18c66d46f2608de0467ad3df0268Mopria                ParcelFileDescriptor output) {
1763bf66744d61d18c66d46f2608de0467ad3df0268Mopria            mPage = page;
1773bf66744d61d18c66d46f2608de0467ad3df0268Mopria            mWidth = width;
1783bf66744d61d18c66d46f2608de0467ad3df0268Mopria            mYOffset = y;
1793bf66744d61d18c66d46f2608de0467ad3df0268Mopria            mHeight = height;
1803bf66744d61d18c66d46f2608de0467ad3df0268Mopria            mZoomFactor = zoom;
1813bf66744d61d18c66d46f2608de0467ad3df0268Mopria            mOutput = output;
1823bf66744d61d18c66d46f2608de0467ad3df0268Mopria
1833bf66744d61d18c66d46f2608de0467ad3df0268Mopria            // Buffer will temporarily hold RGBA data from Bitmap
1843bf66744d61d18c66d46f2608de0467ad3df0268Mopria            mRowsPerStripe = MAX_BYTES_PER_CHUNK / mWidth / 4;
1853bf66744d61d18c66d46f2608de0467ad3df0268Mopria            mBuffer = ByteBuffer.allocate(mWidth * mRowsPerStripe * 4);
1863bf66744d61d18c66d46f2608de0467ad3df0268Mopria        }
1873bf66744d61d18c66d46f2608de0467ad3df0268Mopria
1883bf66744d61d18c66d46f2608de0467ad3df0268Mopria        @Override
1893bf66744d61d18c66d46f2608de0467ad3df0268Mopria        public void run() {
1903bf66744d61d18c66d46f2608de0467ad3df0268Mopria            Bitmap bitmap = null;
1913bf66744d61d18c66d46f2608de0467ad3df0268Mopria
1923bf66744d61d18c66d46f2608de0467ad3df0268Mopria            // Make sure nobody closes page while we're using it
1933bf66744d61d18c66d46f2608de0467ad3df0268Mopria            synchronized(mPageOpenLock) {
1943bf66744d61d18c66d46f2608de0467ad3df0268Mopria                try (OutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(
1953bf66744d61d18c66d46f2608de0467ad3df0268Mopria                        mOutput)) {
1963bf66744d61d18c66d46f2608de0467ad3df0268Mopria                    if (mPage == null) {
1973bf66744d61d18c66d46f2608de0467ad3df0268Mopria                        // If page was closed before we synchronized, this closes the outputStream
1983bf66744d61d18c66d46f2608de0467ad3df0268Mopria                        Log.e(TAG, "Page lost");
1993bf66744d61d18c66d46f2608de0467ad3df0268Mopria                        return;
2003bf66744d61d18c66d46f2608de0467ad3df0268Mopria                    }
2013bf66744d61d18c66d46f2608de0467ad3df0268Mopria                    // Allocate and clear bitmap to white with no transparency
2023bf66744d61d18c66d46f2608de0467ad3df0268Mopria                    bitmap = Bitmap.createBitmap(mWidth, mRowsPerStripe, Bitmap.Config.ARGB_8888);
2033bf66744d61d18c66d46f2608de0467ad3df0268Mopria
2043bf66744d61d18c66d46f2608de0467ad3df0268Mopria                    // Render each stripe to output
2053bf66744d61d18c66d46f2608de0467ad3df0268Mopria                    for (int startRow = mYOffset; startRow < mYOffset + mHeight; startRow +=
2063bf66744d61d18c66d46f2608de0467ad3df0268Mopria                            mRowsPerStripe) {
2073bf66744d61d18c66d46f2608de0467ad3df0268Mopria                        int stripeRows = Math.min(mRowsPerStripe, (mYOffset + mHeight) - startRow);
2083bf66744d61d18c66d46f2608de0467ad3df0268Mopria                        renderToBitmap(startRow, bitmap);
2093bf66744d61d18c66d46f2608de0467ad3df0268Mopria                        writeRgb(bitmap, stripeRows, outputStream);
2103bf66744d61d18c66d46f2608de0467ad3df0268Mopria                    }
2113bf66744d61d18c66d46f2608de0467ad3df0268Mopria                } catch (IOException e) {
2123bf66744d61d18c66d46f2608de0467ad3df0268Mopria                    Log.e(TAG, "Failed to write", e);
2133bf66744d61d18c66d46f2608de0467ad3df0268Mopria                } finally {
2143bf66744d61d18c66d46f2608de0467ad3df0268Mopria                    if (bitmap != null) bitmap.recycle();
2153bf66744d61d18c66d46f2608de0467ad3df0268Mopria                }
2163bf66744d61d18c66d46f2608de0467ad3df0268Mopria            }
2173bf66744d61d18c66d46f2608de0467ad3df0268Mopria        }
2183bf66744d61d18c66d46f2608de0467ad3df0268Mopria
2193bf66744d61d18c66d46f2608de0467ad3df0268Mopria        /** From the specified starting row, render from the current page into the target bitmap */
2203bf66744d61d18c66d46f2608de0467ad3df0268Mopria        private void renderToBitmap(int startRow, Bitmap bitmap) {
2213bf66744d61d18c66d46f2608de0467ad3df0268Mopria            Matrix matrix = new Matrix();
2223bf66744d61d18c66d46f2608de0467ad3df0268Mopria            // The scaling matrix increases DPI (default is 72dpi) to page output
2233bf66744d61d18c66d46f2608de0467ad3df0268Mopria            matrix.setScale((float) mZoomFactor, (float) mZoomFactor);
2243bf66744d61d18c66d46f2608de0467ad3df0268Mopria            // The translate specifies adjusts which part of the page we are rendering
2253bf66744d61d18c66d46f2608de0467ad3df0268Mopria            matrix.postTranslate(0, 0 - startRow);
2263bf66744d61d18c66d46f2608de0467ad3df0268Mopria            bitmap.eraseColor(0xFFFFFFFF);
2273bf66744d61d18c66d46f2608de0467ad3df0268Mopria
2283bf66744d61d18c66d46f2608de0467ad3df0268Mopria            mPage.render(bitmap, null, matrix, PdfRenderer.Page.RENDER_MODE_FOR_PRINT);
2293bf66744d61d18c66d46f2608de0467ad3df0268Mopria        }
2303bf66744d61d18c66d46f2608de0467ad3df0268Mopria
2313bf66744d61d18c66d46f2608de0467ad3df0268Mopria        /** Copy rows of RGB bytes from the bitmap to the output stream */
2323bf66744d61d18c66d46f2608de0467ad3df0268Mopria        private void writeRgb(Bitmap bitmap, int rows, OutputStream out)
2333bf66744d61d18c66d46f2608de0467ad3df0268Mopria                throws IOException {
2343bf66744d61d18c66d46f2608de0467ad3df0268Mopria            mBuffer.clear();
2353bf66744d61d18c66d46f2608de0467ad3df0268Mopria            bitmap.copyPixelsToBuffer(mBuffer);
2363bf66744d61d18c66d46f2608de0467ad3df0268Mopria            int alphaPixelSize = mWidth * rows * 4;
2373bf66744d61d18c66d46f2608de0467ad3df0268Mopria
2383bf66744d61d18c66d46f2608de0467ad3df0268Mopria            // Chop out the alpha byte
2393bf66744d61d18c66d46f2608de0467ad3df0268Mopria            byte array[] = mBuffer.array();
2403bf66744d61d18c66d46f2608de0467ad3df0268Mopria            int from, to;
2413bf66744d61d18c66d46f2608de0467ad3df0268Mopria            for (from = 0, to = 0; from < alphaPixelSize; from += 4, to += 3) {
2423bf66744d61d18c66d46f2608de0467ad3df0268Mopria                array[to] = array[from];
2433bf66744d61d18c66d46f2608de0467ad3df0268Mopria                array[to + 1] = array[from + 1];
2443bf66744d61d18c66d46f2608de0467ad3df0268Mopria                array[to + 2] = array[from + 2];
2453bf66744d61d18c66d46f2608de0467ad3df0268Mopria            }
2463bf66744d61d18c66d46f2608de0467ad3df0268Mopria
2473bf66744d61d18c66d46f2608de0467ad3df0268Mopria            // Write it
2483bf66744d61d18c66d46f2608de0467ad3df0268Mopria            out.write(mBuffer.array(), 0, to);
2493bf66744d61d18c66d46f2608de0467ad3df0268Mopria        }
2503bf66744d61d18c66d46f2608de0467ad3df0268Mopria    }
2513bf66744d61d18c66d46f2608de0467ad3df0268Mopria}