PageContentRepository.java revision 525a66b2bb5abf844aff2109bdc9ed819566bece
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.printspooler.model;
18
19import android.app.ActivityManager;
20import android.content.Context;
21import android.content.res.Configuration;
22import android.graphics.Bitmap;
23import android.graphics.Color;
24import android.graphics.Matrix;
25import android.graphics.Rect;
26import android.graphics.drawable.BitmapDrawable;
27import android.graphics.pdf.PdfRenderer;
28import android.os.AsyncTask;
29import android.os.Debug;
30import android.os.ParcelFileDescriptor;
31import android.print.PrintAttributes.MediaSize;
32import android.print.PrintAttributes.Margins;
33import android.print.PrintDocumentInfo;
34import android.util.ArrayMap;
35import android.util.Log;
36import android.view.View;
37import dalvik.system.CloseGuard;
38
39import java.io.IOException;
40import java.util.Iterator;
41import java.util.LinkedHashMap;
42import java.util.Map;
43
44public final class PageContentRepository {
45    private static final String LOG_TAG = "PageContentRepository";
46
47    private static final boolean DEBUG = true;
48
49    private static final int INVALID_PAGE_INDEX = -1;
50
51    private static final int STATE_CLOSED = 0;
52    private static final int STATE_OPENED = 1;
53    private static final int STATE_DESTROYED = 2;
54
55    private static final int BYTES_PER_PIXEL = 4;
56
57    private static final int BYTES_PER_MEGABYTE = 1048576;
58
59    private static final int MILS_PER_INCH = 1000;
60    private static final int POINTS_IN_INCH = 72;
61
62    private final CloseGuard mCloseGuard = CloseGuard.get();
63
64    private final ArrayMap<Integer, PageContentProvider> mPageContentProviders =
65            new ArrayMap<>();
66
67    private final AsyncRenderer mRenderer;
68
69    private RenderSpec mLastRenderSpec;
70
71    private int mScheduledPreloadFirstShownPage = INVALID_PAGE_INDEX;
72
73    private int mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX;
74
75    private int mState;
76
77    public interface OnPageContentAvailableCallback {
78        public void onPageContentAvailable(BitmapDrawable content);
79    }
80
81    public PageContentRepository(Context context) {
82        mRenderer = new AsyncRenderer(context);
83        mState = STATE_CLOSED;
84        if (DEBUG) {
85            Log.i(LOG_TAG, "STATE_CLOSED");
86        }
87        mCloseGuard.open("destroy");
88    }
89
90    public void open(ParcelFileDescriptor source, final Runnable callback) {
91        throwIfNotClosed();
92        mState = STATE_OPENED;
93        if (DEBUG) {
94            Log.i(LOG_TAG, "STATE_OPENED");
95        }
96        mRenderer.open(source, callback);
97    }
98
99    public void close(Runnable callback) {
100        throwIfNotOpened();
101        mState = STATE_CLOSED;
102        if (DEBUG) {
103            Log.i(LOG_TAG, "STATE_CLOSED");
104        }
105
106        mRenderer.close(callback);
107    }
108
109    public void destroy() {
110        throwIfNotClosed();
111        mState = STATE_DESTROYED;
112        if (DEBUG) {
113            Log.i(LOG_TAG, "STATE_DESTROYED");
114        }
115        throwIfNotClosed();
116        doDestroy();
117    }
118
119    public void startPreload(int firstShownPage, int lastShownPage) {
120        // If we do not have a render spec we have no clue what size the
121        // preloaded bitmaps should be, so just take a note for what to do.
122        if (mLastRenderSpec == null) {
123            mScheduledPreloadFirstShownPage = firstShownPage;
124            mScheduledPreloadLastShownPage = lastShownPage;
125        } else {
126            mRenderer.startPreload(firstShownPage, lastShownPage, mLastRenderSpec);
127        }
128    }
129
130    public void stopPreload() {
131        mRenderer.stopPreload();
132    }
133
134    public int getFilePageCount() {
135        return mRenderer.getPageCount();
136    }
137
138    public PageContentProvider peekPageContentProvider(int pageIndex) {
139        return mPageContentProviders.get(pageIndex);
140    }
141
142    public PageContentProvider acquirePageContentProvider(int pageIndex, View owner) {
143        throwIfDestroyed();
144
145        if (DEBUG) {
146            Log.i(LOG_TAG, "Acquiring provider for page: " + pageIndex);
147        }
148
149        if (mPageContentProviders.get(pageIndex)!= null) {
150            throw new IllegalStateException("Already acquired for page: " + pageIndex);
151        }
152
153        PageContentProvider provider = new PageContentProvider(pageIndex, owner);
154
155        mPageContentProviders.put(pageIndex, provider);
156
157        return provider;
158    }
159
160    public void releasePageContentProvider(PageContentProvider provider) {
161        throwIfDestroyed();
162
163        if (DEBUG) {
164            Log.i(LOG_TAG, "Releasing provider for page: " + provider.mPageIndex);
165        }
166
167        if (mPageContentProviders.remove(provider.mPageIndex) == null) {
168            throw new IllegalStateException("Not acquired");
169        }
170
171        provider.cancelLoad();
172    }
173
174    @Override
175    protected void finalize() throws Throwable {
176        try {
177            if (mState != STATE_DESTROYED) {
178                mCloseGuard.warnIfOpen();
179                doDestroy();
180            }
181        } finally {
182            super.finalize();
183        }
184    }
185
186    private void doDestroy() {
187        mState = STATE_DESTROYED;
188        if (DEBUG) {
189            Log.i(LOG_TAG, "STATE_DESTROYED");
190        }
191        mRenderer.destroy();
192    }
193
194    private void throwIfNotOpened() {
195        if (mState != STATE_OPENED) {
196            throw new IllegalStateException("Not opened");
197        }
198    }
199
200    private void throwIfNotClosed() {
201        if (mState != STATE_CLOSED) {
202            throw new IllegalStateException("Not closed");
203        }
204    }
205
206    private void throwIfDestroyed() {
207        if (mState == STATE_DESTROYED) {
208            throw new IllegalStateException("Destroyed");
209        }
210    }
211
212    public final class PageContentProvider {
213        private final int mPageIndex;
214        private View mOwner;
215
216        public PageContentProvider(int pageIndex, View owner) {
217            mPageIndex = pageIndex;
218            mOwner = owner;
219        }
220
221        public View getOwner() {
222            return mOwner;
223        }
224
225        public int getPageIndex() {
226            return mPageIndex;
227        }
228
229        public void getPageContent(RenderSpec renderSpec, OnPageContentAvailableCallback callback) {
230            throwIfDestroyed();
231
232            mLastRenderSpec = renderSpec;
233
234            // We tired to preload but didn't know the bitmap size, now
235            // that we know let us do the work.
236            if (mScheduledPreloadFirstShownPage != INVALID_PAGE_INDEX
237                    && mScheduledPreloadLastShownPage != INVALID_PAGE_INDEX) {
238                startPreload(mScheduledPreloadFirstShownPage, mScheduledPreloadLastShownPage);
239                mScheduledPreloadFirstShownPage = INVALID_PAGE_INDEX;
240                mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX;
241            }
242
243            if (mState == STATE_OPENED) {
244                mRenderer.renderPage(mPageIndex, renderSpec, callback);
245            } else {
246                mRenderer.getCachedPage(mPageIndex, renderSpec, callback);
247            }
248        }
249
250        void cancelLoad() {
251            throwIfDestroyed();
252
253            if (mState == STATE_OPENED) {
254                mRenderer.cancelRendering(mPageIndex);
255            }
256        }
257    }
258
259    private static final class PageContentLruCache {
260        private final LinkedHashMap<Integer, RenderedPage> mRenderedPages =
261                new LinkedHashMap<>();
262
263        private final int mMaxSizeInBytes;
264
265        private int mSizeInBytes;
266
267        public PageContentLruCache(int maxSizeInBytes) {
268            mMaxSizeInBytes = maxSizeInBytes;
269        }
270
271        public RenderedPage getRenderedPage(int pageIndex) {
272            return mRenderedPages.get(pageIndex);
273        }
274
275        public RenderedPage removeRenderedPage(int pageIndex) {
276            RenderedPage page = mRenderedPages.remove(pageIndex);
277            if (page != null) {
278                mSizeInBytes -= page.getSizeInBytes();
279            }
280            return page;
281        }
282
283        public RenderedPage putRenderedPage(int pageIndex, RenderedPage renderedPage) {
284            RenderedPage oldRenderedPage = mRenderedPages.remove(pageIndex);
285            if (oldRenderedPage != null) {
286                if (!oldRenderedPage.renderSpec.equals(renderedPage.renderSpec)) {
287                    throw new IllegalStateException("Wrong page size");
288                }
289            } else {
290                final int contentSizeInBytes = renderedPage.getSizeInBytes();
291                if (mSizeInBytes + contentSizeInBytes > mMaxSizeInBytes) {
292                    throw new IllegalStateException("Client didn't free space");
293                }
294
295                mSizeInBytes += contentSizeInBytes;
296            }
297            return mRenderedPages.put(pageIndex, renderedPage);
298        }
299
300        public void invalidate() {
301            for (Map.Entry<Integer, RenderedPage> entry : mRenderedPages.entrySet()) {
302                entry.getValue().state = RenderedPage.STATE_SCRAP;
303            }
304        }
305
306        public RenderedPage removeLeastNeeded() {
307            if (mRenderedPages.isEmpty()) {
308                return null;
309            }
310
311            // First try to remove a rendered page that holds invalidated
312            // or incomplete content, i.e. its render spec is null.
313            for (Map.Entry<Integer, RenderedPage> entry : mRenderedPages.entrySet()) {
314                RenderedPage renderedPage = entry.getValue();
315                if (renderedPage.state == RenderedPage.STATE_SCRAP) {
316                    Integer pageIndex = entry.getKey();
317                    mRenderedPages.remove(pageIndex);
318                    mSizeInBytes -= renderedPage.getSizeInBytes();
319                    return renderedPage;
320                }
321            }
322
323            // If all rendered pages contain rendered content, then use the oldest.
324            final int pageIndex = mRenderedPages.eldest().getKey();
325            RenderedPage renderedPage = mRenderedPages.remove(pageIndex);
326            mSizeInBytes -= renderedPage.getSizeInBytes();
327            return renderedPage;
328        }
329
330        public int getSizeInBytes() {
331            return mSizeInBytes;
332        }
333
334        public int getMaxSizeInBytes() {
335            return mMaxSizeInBytes;
336        }
337
338        public void clear() {
339            Iterator<Map.Entry<Integer, RenderedPage>> iterator =
340                    mRenderedPages.entrySet().iterator();
341            while (iterator.hasNext()) {
342                iterator.next().getValue().recycle();
343                iterator.remove();
344            }
345        }
346    }
347
348    public static final class RenderSpec {
349        final int bitmapWidth;
350        final int bitmapHeight;
351        final MediaSize mediaSize;
352        final Margins minMargins;
353
354        public RenderSpec(int bitmapWidth, int bitmapHeight,
355                MediaSize mediaSize, Margins minMargins) {
356            this.bitmapWidth = bitmapWidth;
357            this.bitmapHeight = bitmapHeight;
358            this.mediaSize = mediaSize;
359            this.minMargins = minMargins;
360        }
361
362        @Override
363        public boolean equals(Object obj) {
364            if (this == obj) {
365                return true;
366            }
367            if (obj == null) {
368                return false;
369            }
370            if (getClass() != obj.getClass()) {
371                return false;
372            }
373            RenderSpec other = (RenderSpec) obj;
374            if (bitmapHeight != other.bitmapHeight) {
375                return false;
376            }
377            if (bitmapWidth != other.bitmapWidth) {
378                return false;
379            }
380            if (mediaSize != null) {
381                if (!mediaSize.equals(other.mediaSize)) {
382                    return false;
383                }
384            } else if (other.mediaSize != null) {
385                return false;
386            }
387            if (minMargins != null) {
388                if (!minMargins.equals(other.minMargins)) {
389                    return false;
390                }
391            } else if (other.minMargins != null) {
392                return false;
393            }
394            return true;
395        }
396
397        public boolean hasSameSize(RenderedPage page) {
398            Bitmap bitmap = page.content.getBitmap();
399            return bitmap.getWidth() == bitmapWidth
400                    && bitmap.getHeight() == bitmapHeight;
401        }
402
403        @Override
404        public int hashCode() {
405            int result = bitmapWidth;
406            result = 31 * result + bitmapHeight;
407            result = 31 * result + (mediaSize != null ? mediaSize.hashCode() : 0);
408            result = 31 * result + (minMargins != null ? minMargins.hashCode() : 0);
409            return result;
410        }
411    }
412
413    private static final class RenderedPage {
414        public static final int STATE_RENDERED = 0;
415        public static final int STATE_RENDERING = 1;
416        public static final int STATE_SCRAP = 2;
417
418        final BitmapDrawable content;
419        RenderSpec renderSpec;
420
421        int state = STATE_SCRAP;
422
423        RenderedPage(BitmapDrawable content) {
424            this.content = content;
425        }
426
427        public int getSizeInBytes() {
428            return content.getBitmap().getByteCount();
429        }
430
431        public void recycle() {
432            content.getBitmap().recycle();
433        }
434
435        public void erase() {
436            content.getBitmap().eraseColor(Color.WHITE);
437        }
438    }
439
440    private static int pointsFromMils(int mils) {
441        return (int) (((float) mils / MILS_PER_INCH) * POINTS_IN_INCH);
442    }
443
444    private static class AsyncRenderer {
445        private final Context mContext;
446
447        private final PageContentLruCache mPageContentCache;
448
449        private final ArrayMap<Integer, RenderPageTask> mPageToRenderTaskMap = new ArrayMap<>();
450
451        private int mPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
452
453        // Accessed only by the executor thread.
454        private PdfRenderer mRenderer;
455
456        public AsyncRenderer(Context context) {
457            mContext = context;
458
459            ActivityManager activityManager = (ActivityManager)
460                    mContext.getSystemService(Context.ACTIVITY_SERVICE);
461            final int cacheSizeInBytes = activityManager.getMemoryClass() * BYTES_PER_MEGABYTE / 4;
462            mPageContentCache = new PageContentLruCache(cacheSizeInBytes);
463        }
464
465        public void open(final ParcelFileDescriptor source, final Runnable callback) {
466            // Opening a new document invalidates the cache as it has pages
467            // from the last document. We keep the cache even when the document
468            // is closed to show pages while the other side is writing the new
469            // document.
470            mPageContentCache.invalidate();
471
472            new AsyncTask<Void, Void, Integer>() {
473                @Override
474                protected Integer doInBackground(Void... params) {
475                    try {
476                        mRenderer = new PdfRenderer(source);
477                        return mRenderer.getPageCount();
478                    } catch (IOException ioe) {
479                        throw new IllegalStateException("Cannot open PDF document");
480                    }
481                }
482
483                @Override
484                public void onPostExecute(Integer pageCount) {
485                    mPageCount = pageCount;
486                    if (callback != null) {
487                        callback.run();
488                    }
489                }
490            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
491        }
492
493        public void close(final Runnable callback) {
494            cancelAllRendering();
495
496            new AsyncTask<Void, Void, Void>() {
497                @Override
498                protected Void doInBackground(Void... params) {
499                    mRenderer.close();
500                    return null;
501                }
502
503                @Override
504                public void onPostExecute(Void result) {
505                    mPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
506                    if (callback != null) {
507                        callback.run();
508                    }
509                }
510            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
511        }
512
513        public void destroy() {
514            mPageContentCache.invalidate();
515            mPageContentCache.clear();
516        }
517
518        public void startPreload(int firstShownPage, int lastShownPage, RenderSpec renderSpec) {
519            if (DEBUG) {
520                Log.i(LOG_TAG, "Preloading pages around [" + firstShownPage
521                        + "-" + lastShownPage + "]");
522            }
523
524            final int bitmapSizeInBytes = renderSpec.bitmapWidth * renderSpec.bitmapHeight
525                    * BYTES_PER_PIXEL;
526            final int maxCachedPageCount = mPageContentCache.getMaxSizeInBytes()
527                    / bitmapSizeInBytes;
528            final int halfPreloadCount = (maxCachedPageCount - (lastShownPage - firstShownPage)) /2;
529
530            final int excessFromStart;
531            if (firstShownPage - halfPreloadCount < 0) {
532                excessFromStart = halfPreloadCount - firstShownPage;
533            } else {
534                excessFromStart = 0;
535            }
536
537            final int excessFromEnd;
538            if (lastShownPage + halfPreloadCount >= mPageCount) {
539                excessFromEnd = (lastShownPage + halfPreloadCount) - mPageCount;
540            } else {
541                excessFromEnd = 0;
542            }
543
544            final int fromIndex = Math.max(firstShownPage - halfPreloadCount - excessFromEnd, 0);
545            final int toIndex = Math.min(lastShownPage + halfPreloadCount + excessFromStart,
546                    mPageCount - 1);
547
548            for (int i = fromIndex; i <= toIndex; i++) {
549                renderPage(i, renderSpec, null);
550            }
551        }
552
553        public void stopPreload() {
554            final int taskCount = mPageToRenderTaskMap.size();
555            for (int i = 0; i < taskCount; i++) {
556                RenderPageTask task = mPageToRenderTaskMap.valueAt(i);
557                if (task.isPreload() && !task.isCancelled()) {
558                    task.cancel(true);
559                }
560            }
561        }
562
563        public int getPageCount() {
564            return mPageCount;
565        }
566
567        public void getCachedPage(int pageIndex, RenderSpec renderSpec,
568                OnPageContentAvailableCallback callback) {
569            RenderedPage renderedPage = mPageContentCache.getRenderedPage(pageIndex);
570            if (renderedPage != null && renderedPage.state == RenderedPage.STATE_RENDERED
571                    && renderedPage.renderSpec.equals(renderSpec)) {
572                if (DEBUG) {
573                    Log.i(LOG_TAG, "Cache hit for page: " + pageIndex);
574                }
575
576                // Announce if needed.
577                if (callback != null) {
578                    callback.onPageContentAvailable(renderedPage.content);
579                }
580            }
581        }
582
583        public void renderPage(int pageIndex, RenderSpec renderSpec,
584                OnPageContentAvailableCallback callback) {
585            // First, check if we have a rendered page for this index.
586            RenderedPage renderedPage = mPageContentCache.getRenderedPage(pageIndex);
587            if (renderedPage != null && renderedPage.state == RenderedPage.STATE_RENDERED) {
588                // If we have rendered page with same constraints - done.
589                if (renderedPage.renderSpec.equals(renderSpec)) {
590                    if (DEBUG) {
591                        Log.i(LOG_TAG, "Cache hit for page: " + pageIndex);
592                    }
593
594                    // Announce if needed.
595                    if (callback != null) {
596                        callback.onPageContentAvailable(renderedPage.content);
597                    }
598                    return;
599                } else {
600                    // If the constraints changed, mark the page obsolete.
601                    renderedPage.state = RenderedPage.STATE_SCRAP;
602                }
603            }
604
605            // Next, check if rendering this page is scheduled.
606            RenderPageTask renderTask = mPageToRenderTaskMap.get(pageIndex);
607            if (renderTask != null && !renderTask.isCancelled()) {
608                // If not rendered and constraints same....
609                if (renderTask.mRenderSpec.equals(renderSpec)) {
610                    if (renderTask.mCallback != null) {
611                        // If someone else is already waiting for this page - bad state.
612                        if (callback != null && renderTask.mCallback != callback) {
613                            throw new IllegalStateException("Page rendering not cancelled");
614                        }
615                    } else {
616                        // No callback means we are preloading so just let the argument
617                        // callback be attached to our work in progress.
618                        renderTask.mCallback = callback;
619                    }
620                    return;
621                } else {
622                    // If not rendered and constraints changed - cancel rendering.
623                    renderTask.cancel(true);
624                }
625            }
626
627            // Oh well, we will have work to do...
628            renderTask = new RenderPageTask(pageIndex, renderSpec, callback);
629            mPageToRenderTaskMap.put(pageIndex, renderTask);
630            renderTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
631        }
632
633        public void cancelRendering(int pageIndex) {
634            RenderPageTask task = mPageToRenderTaskMap.get(pageIndex);
635            if (task != null && !task.isCancelled()) {
636                task.cancel(true);
637            }
638        }
639
640        private void cancelAllRendering() {
641            final int taskCount = mPageToRenderTaskMap.size();
642            for (int i = 0; i < taskCount; i++) {
643                RenderPageTask task = mPageToRenderTaskMap.valueAt(i);
644                if (!task.isCancelled()) {
645                    task.cancel(true);
646                }
647            }
648        }
649
650        private final class RenderPageTask extends AsyncTask<Void, Void, RenderedPage> {
651            final int mPageIndex;
652            final RenderSpec mRenderSpec;
653            OnPageContentAvailableCallback mCallback;
654            RenderedPage mRenderedPage;
655
656            public RenderPageTask(int pageIndex, RenderSpec renderSpec,
657                    OnPageContentAvailableCallback callback) {
658                mPageIndex = pageIndex;
659                mRenderSpec = renderSpec;
660                mCallback = callback;
661            }
662
663            @Override
664            protected void onPreExecute() {
665                mRenderedPage = mPageContentCache.getRenderedPage(mPageIndex);
666                if (mRenderedPage != null && mRenderedPage.state == RenderedPage.STATE_RENDERED) {
667                    throw new IllegalStateException("Trying to render a rendered page");
668                }
669
670                // Reuse bitmap for the page only if the right size.
671                if (mRenderedPage != null && !mRenderSpec.hasSameSize(mRenderedPage)) {
672                    if (DEBUG) {
673                        Log.i(LOG_TAG, "Recycling bitmap for page: " + mPageIndex
674                                + " with different size.");
675                    }
676                    mPageContentCache.removeRenderedPage(mPageIndex);
677                    mRenderedPage.recycle();
678                    mRenderedPage = null;
679                }
680
681                final int bitmapSizeInBytes = mRenderSpec.bitmapWidth
682                        * mRenderSpec.bitmapHeight * BYTES_PER_PIXEL;
683
684                // Try to find a bitmap to reuse.
685                while (mRenderedPage == null) {
686
687                    // Fill the cache greedily.
688                    if (mPageContentCache.getSizeInBytes() <= 0
689                            || mPageContentCache.getSizeInBytes() + bitmapSizeInBytes
690                            <= mPageContentCache.getMaxSizeInBytes()) {
691                        break;
692                    }
693
694                    RenderedPage renderedPage = mPageContentCache.removeLeastNeeded();
695
696                    if (!mRenderSpec.hasSameSize(renderedPage)) {
697                        if (DEBUG) {
698                            Log.i(LOG_TAG, "Recycling bitmap for page: " + mPageIndex
699                                   + " with different size.");
700                        }
701                        renderedPage.recycle();
702                        continue;
703                    }
704
705                    mRenderedPage = renderedPage;
706                    renderedPage.erase();
707
708                    if (DEBUG) {
709                        Log.i(LOG_TAG, "Reused bitmap for page: " + mPageIndex + " cache size: "
710                                + mPageContentCache.getSizeInBytes() + " bytes");
711                    }
712
713                    break;
714                }
715
716//                if (mRenderedPage == null) {
717//                    final int bitmapSizeInBytes = mRenderSpec.bitmapWidth
718//                            * mRenderSpec.bitmapHeight * BYTES_PER_PIXEL;
719//
720//                    while (mPageContentCache.getSizeInBytes() > 0
721//                            && mPageContentCache.getSizeInBytes() + bitmapSizeInBytes
722//                            > mPageContentCache.getMaxSizeInBytes()) {
723//                        RenderedPage renderedPage = mPageContentCache.removeLeastNeeded();
724//
725//                        // If not the right size - recycle and keep freeing space.
726//                        Bitmap bitmap = renderedPage.content.getBitmap();
727//                        if (!mRenderSpec.hasSameSize(bitmap.getWidth(), bitmap.getHeight())) {
728//                            if (DEBUG) {
729//                                Log.i(LOG_TAG, "Recycling bitmap for page: " + mPageIndex
730//                                        + " with different size.");
731//                            }
732//                            bitmap.recycle();
733//                            continue;
734//                        }
735//
736//                        mRenderedPage = renderedPage;
737//                        bitmap.eraseColor(Color.WHITE);
738//
739//                        if (DEBUG) {
740//                            Log.i(LOG_TAG, "Reused bitmap for page: " + mPageIndex + " cache size: "
741//                                    + mPageContentCache.getSizeInBytes() + " bytes");
742//                        }
743//                    }
744//                }
745
746                if (mRenderedPage == null) {
747                    if (DEBUG) {
748                        Log.i(LOG_TAG, "Created bitmap for page: " + mPageIndex + " cache size: "
749                                + mPageContentCache.getSizeInBytes() + " bytes");
750                    }
751                    Bitmap bitmap = Bitmap.createBitmap(mRenderSpec.bitmapWidth,
752                            mRenderSpec.bitmapHeight, Bitmap.Config.ARGB_8888);
753                    bitmap.eraseColor(Color.WHITE);
754                    BitmapDrawable content = new BitmapDrawable(mContext.getResources(), bitmap);
755                    mRenderedPage = new RenderedPage(content);
756                }
757
758                mRenderedPage.renderSpec = mRenderSpec;
759                mRenderedPage.state = RenderedPage.STATE_RENDERING;
760
761                mPageContentCache.putRenderedPage(mPageIndex, mRenderedPage);
762            }
763
764            @Override
765            protected RenderedPage doInBackground(Void... params) {
766                if (isCancelled()) {
767                    return mRenderedPage;
768                }
769
770                PdfRenderer.Page page = mRenderer.openPage(mPageIndex);
771
772                if (isCancelled()) {
773                    page.close();
774                    return mRenderedPage;
775                }
776
777                Bitmap bitmap = mRenderedPage.content.getBitmap();
778
779                final int srcWidthPts = page.getWidth();
780                final int srcHeightPts = page.getHeight();
781
782                final int dstWidthPts = pointsFromMils(mRenderSpec.mediaSize.getWidthMils());
783                final int dstHeightPts = pointsFromMils(mRenderSpec.mediaSize.getHeightMils());
784
785                final boolean scaleContent = mRenderer.shouldScaleForPrinting();
786                final boolean contentLandscape = !mRenderSpec.mediaSize.isPortrait();
787
788                final float displayScale;
789                Matrix matrix = new Matrix();
790
791                if (scaleContent) {
792                    displayScale = Math.min((float) bitmap.getWidth() / srcWidthPts,
793                            (float) bitmap.getHeight() / srcHeightPts);
794                } else {
795                    if (contentLandscape) {
796                        displayScale = (float) bitmap.getHeight() / dstHeightPts;
797                    } else {
798                        displayScale = (float) bitmap.getWidth() / dstWidthPts;
799                    }
800                }
801                matrix.postScale(displayScale, displayScale);
802
803                Configuration configuration = mContext.getResources().getConfiguration();
804                if (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
805                    matrix.postTranslate(bitmap.getWidth() - srcWidthPts * displayScale, 0);
806                }
807
808                final int paddingLeftPts = pointsFromMils(mRenderSpec.minMargins.getLeftMils());
809                final int paddingTopPts = pointsFromMils(mRenderSpec.minMargins.getTopMils());
810                final int paddingRightPts = pointsFromMils(mRenderSpec.minMargins.getRightMils());
811                final int paddingBottomPts = pointsFromMils(mRenderSpec.minMargins.getBottomMils());
812
813                Rect clip = new Rect();
814                clip.left = (int) (paddingLeftPts * displayScale);
815                clip.top = (int) (paddingTopPts * displayScale);
816                clip.right = (int) (bitmap.getWidth() - paddingRightPts * displayScale);
817                clip.bottom = (int) (bitmap.getHeight() - paddingBottomPts * displayScale);
818
819                if (DEBUG) {
820                    Log.i(LOG_TAG, "Rendering page:" + mPageIndex + " of " + mPageCount);
821                }
822
823                page.render(bitmap, clip, matrix, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
824
825                page.close();
826
827                return mRenderedPage;
828            }
829
830            @Override
831            public void onPostExecute(RenderedPage renderedPage) {
832                if (DEBUG) {
833                    Log.i(LOG_TAG, "Completed rendering page: " + mPageIndex);
834                }
835
836                // This task is done.
837                mPageToRenderTaskMap.remove(mPageIndex);
838
839                // Take a note that the content is rendered.
840                renderedPage.state = RenderedPage.STATE_RENDERED;
841
842                // Announce success if needed.
843                if (mCallback != null) {
844                    mCallback.onPageContentAvailable(renderedPage.content);
845                }
846            }
847
848            @Override
849            protected void onCancelled(RenderedPage renderedPage) {
850                if (DEBUG) {
851                    Log.i(LOG_TAG, "Cancelled rendering page: " + mPageIndex);
852                }
853
854                // This task is done.
855                mPageToRenderTaskMap.remove(mPageIndex);
856
857                // If canceled before on pre-execute.
858                if (renderedPage == null) {
859                    return;
860                }
861
862                // Take a note that the content is not rendered.
863                renderedPage.state = RenderedPage.STATE_SCRAP;
864            }
865
866            public boolean isPreload() {
867                return mCallback == null;
868            }
869        }
870    }
871}
872