diff options
| author | 2016-03-23 17:17:39 +0000 | |
|---|---|---|
| committer | 2016-03-23 17:17:40 +0000 | |
| commit | 49435a72955fd6d2673ac33c34e2417c96fd87fb (patch) | |
| tree | a173dc392b612a41e50df2bb29d1c9c18387629b | |
| parent | 8aa976b60b0b638a77f9007fa6d1a247815d4b09 (diff) | |
| parent | 066bf81b983ce23b91d19b85b7c37a61fba7a9a6 (diff) | |
Merge "Deal with print-preview renderings that do not match the correct number of pages." into nyc-dev
8 files changed, 196 insertions, 79 deletions
diff --git a/packages/PrintSpooler/jni/com_android_printspooler_util_BitmapSerializeUtils.cpp b/packages/PrintSpooler/jni/com_android_printspooler_util_BitmapSerializeUtils.cpp index 1530a02c22fe..1ce3949bbb81 100644 --- a/packages/PrintSpooler/jni/com_android_printspooler_util_BitmapSerializeUtils.cpp +++ b/packages/PrintSpooler/jni/com_android_printspooler_util_BitmapSerializeUtils.cpp @@ -50,6 +50,10 @@ static bool readAllBytes(const int fd, void* buffer, const size_t byteCount) { size_t remainingBytes = byteCount; while (remainingBytes > 0) { ssize_t readByteCount = read(fd, readBuffer, remainingBytes); + + remainingBytes -= readByteCount; + readBuffer += readByteCount; + if (readByteCount == -1) { if (errno == EINTR) { continue; @@ -57,9 +61,12 @@ static bool readAllBytes(const int fd, void* buffer, const size_t byteCount) { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error reading from buffer: %d", errno); return false; + } else if (readByteCount == 0 && remainingBytes > 0) { + __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, + "File closed before all bytes were read. %zu/%zu remaining", remainingBytes, + byteCount); + return false; } - remainingBytes -= readByteCount; - readBuffer += readByteCount; } return true; } diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/packages/PrintSpooler/res/drawable/print_warning.xml new file mode 100644 index 000000000000..35f0fed78a7f --- /dev/null +++ b/packages/PrintSpooler/res/drawable/print_warning.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="96dp" + android:height="96dp" + android:viewportWidth="96.0" + android:viewportHeight="96.0"> + <path + android:fillColor="#C8CCCE" + android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/> +</vector> diff --git a/packages/PrintSpooler/res/layout/preview_page_error.xml b/packages/PrintSpooler/res/layout/preview_page_error.xml new file mode 100644 index 000000000000..4e9fb7787010 --- /dev/null +++ b/packages/PrintSpooler/res/layout/preview_page_error.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" + android:gravity="center"> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="12dip" + android:src="@drawable/print_warning" + android:contentDescription="@null" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dip" + android:layout_marginEnd="16dip" + android:gravity="center_horizontal" + android:textColor="@android:color/black" + android:text="@string/print_cannot_load_page" /> + +</LinearLayout> diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml index 4b566221e4fa..454c99d6a441 100644 --- a/packages/PrintSpooler/res/values/strings.xml +++ b/packages/PrintSpooler/res/values/strings.xml @@ -288,6 +288,10 @@ <!-- Message for the currently selected printer being unavailable. [CHAR LIMIT=100] --> <string name="print_error_printer_unavailable">This printer isn\'t available right now.</string> + <!-- Message for the case when a preview of a page cannot be loaded because the printing app + provided a broken print preview rendering for this page. [CHAR LIMIT=50] --> + <string name="print_cannot_load_page">Can\'t display preview</string> + <!-- Long running operations --> <!-- Message long running operation when preparing print preview. [CHAR LIMIT=50] --> diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java index f8b134300908..bb359176bdf1 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java +++ b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java @@ -75,7 +75,7 @@ public final class PageContentRepository { private int mState; public interface OnPageContentAvailableCallback { - public void onPageContentAvailable(BitmapDrawable content); + void onPageContentAvailable(BitmapDrawable content); } public PageContentRepository(Context context) { @@ -741,6 +741,7 @@ public final class PageContentRepository { final RenderSpec mRenderSpec; OnPageContentAvailableCallback mCallback; RenderedPage mRenderedPage; + private boolean mIsFailed; public RenderPageTask(int pageIndex, RenderSpec renderSpec, OnPageContentAvailableCallback callback) { @@ -826,25 +827,24 @@ public final class PageContentRepository { Bitmap bitmap = mRenderedPage.content.getBitmap(); - ParcelFileDescriptor[] pipe = null; + ParcelFileDescriptor[] pipe; try { pipe = ParcelFileDescriptor.createPipe(); - ParcelFileDescriptor source = pipe[0]; - ParcelFileDescriptor destination = pipe[1]; - - mRenderer.renderPage(mPageIndex, bitmap.getWidth(), bitmap.getHeight(), - mRenderSpec.printAttributes, destination); - - // We passed the file descriptor to the other side which took - // ownership, so close our copy for the write to complete. - destination.close(); - - BitmapSerializeUtils.readBitmapPixels(bitmap, source); - } catch (IOException|RemoteException e) { - Log.e(LOG_TAG, "Error rendering page:" + mPageIndex, e); - } finally { - IoUtils.closeQuietly(pipe[0]); - IoUtils.closeQuietly(pipe[1]); + + try (ParcelFileDescriptor source = pipe[0]) { + try (ParcelFileDescriptor destination = pipe[1]) { + + mRenderer.renderPage(mPageIndex, bitmap.getWidth(), bitmap.getHeight(), + mRenderSpec.printAttributes, destination); + } + + BitmapSerializeUtils.readBitmapPixels(bitmap, source); + } + + mIsFailed = false; + } catch (IOException|RemoteException|IllegalStateException e) { + Log.e(LOG_TAG, "Error rendering page " + mPageIndex, e); + mIsFailed = true; } return mRenderedPage; @@ -859,15 +859,22 @@ public final class PageContentRepository { // This task is done. mPageToRenderTaskMap.remove(mPageIndex); - // Take a note that the content is rendered. - renderedPage.state = RenderedPage.STATE_RENDERED; + if (mIsFailed) { + renderedPage.state = RenderedPage.STATE_SCRAP; + } else { + renderedPage.state = RenderedPage.STATE_RENDERED; + } // Invalidate all caches of the old state of the bitmap mRenderedPage.content.invalidateSelf(); // Announce success if needed. if (mCallback != null) { - mCallback.onPageContentAvailable(renderedPage.content); + if (mIsFailed) { + mCallback.onPageContentAvailable(null); + } else { + mCallback.onPageContentAvailable(renderedPage.content); + } } } diff --git a/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java b/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java index 7db207498775..af4c34795917 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java +++ b/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java @@ -108,62 +108,65 @@ public final class PdfManipulationService extends Service { try { throwIfNotOpened(); - PdfRenderer.Page page = mRenderer.openPage(pageIndex); + try (PdfRenderer.Page page = mRenderer.openPage(pageIndex)) { + final int srcWidthPts = page.getWidth(); + final int srcHeightPts = page.getHeight(); - final int srcWidthPts = page.getWidth(); - final int srcHeightPts = page.getHeight(); + final int dstWidthPts = pointsFromMils( + attributes.getMediaSize().getWidthMils()); + final int dstHeightPts = pointsFromMils( + attributes.getMediaSize().getHeightMils()); - final int dstWidthPts = pointsFromMils( - attributes.getMediaSize().getWidthMils()); - final int dstHeightPts = pointsFromMils( - attributes.getMediaSize().getHeightMils()); + final boolean scaleContent = mRenderer.shouldScaleForPrinting(); + final boolean contentLandscape = !attributes.getMediaSize().isPortrait(); - final boolean scaleContent = mRenderer.shouldScaleForPrinting(); - final boolean contentLandscape = !attributes.getMediaSize().isPortrait(); + final float displayScale; + Matrix matrix = new Matrix(); - final float displayScale; - Matrix matrix = new Matrix(); - - if (scaleContent) { - displayScale = Math.min((float) bitmapWidth / srcWidthPts, - (float) bitmapHeight / srcHeightPts); - } else { - if (contentLandscape) { - displayScale = (float) bitmapHeight / dstHeightPts; + if (scaleContent) { + displayScale = Math.min((float) bitmapWidth / srcWidthPts, + (float) bitmapHeight / srcHeightPts); } else { - displayScale = (float) bitmapWidth / dstWidthPts; + if (contentLandscape) { + displayScale = (float) bitmapHeight / dstHeightPts; + } else { + displayScale = (float) bitmapWidth / dstWidthPts; + } } - } - matrix.postScale(displayScale, displayScale); + matrix.postScale(displayScale, displayScale); - Configuration configuration = PdfManipulationService.this.getResources() - .getConfiguration(); - if (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { - matrix.postTranslate(bitmapWidth - srcWidthPts * displayScale, 0); - } + Configuration configuration = PdfManipulationService.this.getResources() + .getConfiguration(); + if (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + matrix.postTranslate(bitmapWidth - srcWidthPts * displayScale, 0); + } - Margins minMargins = attributes.getMinMargins(); - final int paddingLeftPts = pointsFromMils(minMargins.getLeftMils()); - final int paddingTopPts = pointsFromMils(minMargins.getTopMils()); - final int paddingRightPts = pointsFromMils(minMargins.getRightMils()); - final int paddingBottomPts = pointsFromMils(minMargins.getBottomMils()); + Margins minMargins = attributes.getMinMargins(); + final int paddingLeftPts = pointsFromMils(minMargins.getLeftMils()); + final int paddingTopPts = pointsFromMils(minMargins.getTopMils()); + final int paddingRightPts = pointsFromMils(minMargins.getRightMils()); + final int paddingBottomPts = pointsFromMils(minMargins.getBottomMils()); - Rect clip = new Rect(); - clip.left = (int) (paddingLeftPts * displayScale); - clip.top = (int) (paddingTopPts * displayScale); - clip.right = (int) (bitmapWidth - paddingRightPts * displayScale); - clip.bottom = (int) (bitmapHeight - paddingBottomPts * displayScale); + Rect clip = new Rect(); + clip.left = (int) (paddingLeftPts * displayScale); + clip.top = (int) (paddingTopPts * displayScale); + clip.right = (int) (bitmapWidth - paddingRightPts * displayScale); + clip.bottom = (int) (bitmapHeight - paddingBottomPts * displayScale); - if (DEBUG) { - Log.i(LOG_TAG, "Rendering page:" + pageIndex); - } + if (DEBUG) { + Log.i(LOG_TAG, "Rendering page:" + pageIndex); + } - Bitmap bitmap = getBitmapForSize(bitmapWidth, bitmapHeight); - page.render(bitmap, clip, matrix, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); + Bitmap bitmap = getBitmapForSize(bitmapWidth, bitmapHeight); + page.render(bitmap, clip, matrix, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); - page.close(); + BitmapSerializeUtils.writeBitmapPixels(bitmap, destination); + } + } catch (Throwable e) { + Log.e(LOG_TAG, "Cannot render page", e); - BitmapSerializeUtils.writeBitmapPixels(bitmap, destination); + // The error is propagated to the caller when it tries to read the bitmap and + // the pipe is closed prematurely } finally { IoUtils.closeQuietly(destination); } diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java index 645e182f0a1d..eebb60ce62e0 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java @@ -93,6 +93,7 @@ public final class PageAdapter extends Adapter<ViewHolder> { private PageRange[] mSelectedPages; private BitmapDrawable mEmptyState; + private BitmapDrawable mErrorState; private int mDocumentPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN; private int mSelectedPageCount; @@ -329,7 +330,7 @@ public final class PageAdapter extends Adapter<ViewHolder> { } else { onSelectedPageNotInFile(pageInDocument); } - content.init(provider, mEmptyState, mMediaSize, mMinMargins); + content.init(provider, mEmptyState, mErrorState, mMediaSize, mMinMargins); if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) >= 0) { page.setSelected(true, false); @@ -448,19 +449,35 @@ public final class PageAdapter extends Adapter<ViewHolder> { // Now update the empty state drawable, as it depends on the page // size and is reused for all views for better performance. LayoutInflater inflater = LayoutInflater.from(mContext); - View content = inflater.inflate(R.layout.preview_page_loading, null, false); - content.measure(MeasureSpec.makeMeasureSpec(mPageContentWidth, MeasureSpec.EXACTLY), + View loadingContent = inflater.inflate(R.layout.preview_page_loading, null, false); + loadingContent.measure(MeasureSpec.makeMeasureSpec(mPageContentWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mPageContentHeight, MeasureSpec.EXACTLY)); - content.layout(0, 0, content.getMeasuredWidth(), content.getMeasuredHeight()); + loadingContent.layout(0, 0, loadingContent.getMeasuredWidth(), + loadingContent.getMeasuredHeight()); - Bitmap bitmap = Bitmap.createBitmap(mPageContentWidth, mPageContentHeight, + Bitmap loadingBitmap = Bitmap.createBitmap(mPageContentWidth, mPageContentHeight, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - content.draw(canvas); + loadingContent.draw(new Canvas(loadingBitmap)); // Do not recycle the old bitmap if such as it may be set as an empty // state to any of the page views. Just let the GC take care of it. - mEmptyState = new BitmapDrawable(mContext.getResources(), bitmap); + mEmptyState = new BitmapDrawable(mContext.getResources(), loadingBitmap); + + // Now update the empty state drawable, as it depends on the page + // size and is reused for all views for better performance. + View errorContent = inflater.inflate(R.layout.preview_page_error, null, false); + errorContent.measure(MeasureSpec.makeMeasureSpec(mPageContentWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(mPageContentHeight, MeasureSpec.EXACTLY)); + errorContent.layout(0, 0, errorContent.getMeasuredWidth(), + errorContent.getMeasuredHeight()); + + Bitmap errorBitmap = Bitmap.createBitmap(mPageContentWidth, mPageContentHeight, + Bitmap.Config.ARGB_8888); + errorContent.draw(new Canvas(errorBitmap)); + + // Do not recycle the old bitmap if such as it may be set as an error + // state to any of the page views. Just let the GC take care of it. + mErrorState = new BitmapDrawable(mContext.getResources(), errorBitmap); } private PageRange[] computeSelectedPages() { @@ -742,7 +759,7 @@ public final class PageAdapter extends Adapter<ViewHolder> { private void recyclePageView(PageContentView page, int pageIndexInAdapter) { PageContentProvider provider = page.getPageContentProvider(); if (provider != null) { - page.init(null, mEmptyState, mMediaSize, mMinMargins); + page.init(null, mEmptyState, mErrorState, mMediaSize, mMinMargins); mPageContentRepository.releasePageContentProvider(provider); } mBoundPagesInAdapter.remove(pageIndexInAdapter); diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java index b79278950eb4..7ef42d149ee6 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java +++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java @@ -44,8 +44,12 @@ public class PageContentView extends View private Drawable mEmptyState; + private Drawable mErrorState; + private boolean mContentRequested; + private boolean mIsFailed; + public PageContentView(Context context, AttributeSet attrs) { super(context, attrs); } @@ -53,19 +57,26 @@ public class PageContentView extends View @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { mContentRequested = false; + requestPageContentIfNeeded(); } @Override - public void onPageContentAvailable(BitmapDrawable content) { - setBackground(content); + public void onPageContentAvailable(BitmapDrawable renderedPage) { + mIsFailed = (renderedPage == null); + + if (mIsFailed) { + setBackground(mErrorState); + } else { + setBackground(renderedPage); + } } public PageContentProvider getPageContentProvider() { return mProvider; } - public void init(PageContentProvider provider, Drawable emptyState, + public void init(PageContentProvider provider, Drawable emptyState, Drawable errorState, MediaSize mediaSize, Margins minMargins) { final boolean providerChanged = (mProvider == null) ? provider != null : !mProvider.equals(provider); @@ -81,17 +92,21 @@ public class PageContentView extends View return; } + mIsFailed = false; mProvider = provider; mMediaSize = mediaSize; mMinMargins = minMargins; mEmptyState = emptyState; + mErrorState = errorState; mContentRequested = false; // If there is no provider we want immediately to switch to // the empty state, so pages with no content appear blank. - if (mProvider == null && getBackground() != mEmptyState) { + if (mProvider == null) { setBackground(mEmptyState); + } else if (mIsFailed) { + setBackground(mErrorState); } requestPageContentIfNeeded(); |