summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/print/PageRange.java24
-rw-r--r--core/java/android/print/PrintAttributes.java34
-rw-r--r--core/java/android/print/PrintManager.java2
-rw-r--r--core/jni/android/graphics/pdf/PdfRenderer.cpp11
-rw-r--r--graphics/java/android/graphics/pdf/PdfRenderer.java30
-rw-r--r--packages/PrintSpooler/Android.mk2
-rw-r--r--packages/PrintSpooler/AndroidManifest.xml8
-rw-r--r--packages/PrintSpooler/res/layout/preview_page.xml58
-rw-r--r--packages/PrintSpooler/res/layout/print_activity.xml518
-rw-r--r--packages/PrintSpooler/res/layout/print_progress_fragment.xml27
-rw-r--r--packages/PrintSpooler/res/values-land/constants.xml2
-rw-r--r--packages/PrintSpooler/res/values-sw600dp-land/constants.xml1
-rw-r--r--packages/PrintSpooler/res/values-sw600dp/constants.xml1
-rw-r--r--packages/PrintSpooler/res/values/colors.xml4
-rw-r--r--packages/PrintSpooler/res/values/constants.xml11
-rw-r--r--packages/PrintSpooler/res/values/strings.xml15
-rw-r--r--packages/PrintSpooler/res/values/themes.xml7
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/model/MutexFileProvider.java110
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java4
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java871
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java113
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java783
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java748
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/PrintErrorFragment.java31
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java388
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewFragment.java12
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/PrintProgressFragment.java32
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java136
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/widget/EmbeddedContentContainer.java45
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java114
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java (renamed from packages/PrintSpooler/src/com/android/printspooler/widget/ContentView.java)170
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/widget/PrintOptionsLayout.java9
32 files changed, 3546 insertions, 775 deletions
diff --git a/core/java/android/print/PageRange.java b/core/java/android/print/PageRange.java
index d6320f0..8c229c5 100644
--- a/core/java/android/print/PageRange.java
+++ b/core/java/android/print/PageRange.java
@@ -78,6 +78,30 @@ public final class PageRange implements Parcelable {
return mEnd;
}
+ /**
+ * Gets whether a page range contains a a given page.
+ *
+ * @param pageIndex The page index.
+ * @return True if the page is within this range.
+ *
+ * @hide
+ */
+ public boolean contains(int pageIndex) {
+ return pageIndex >= mStart && pageIndex <= mEnd;
+ }
+
+ /**
+ * Get the size of this range which is the number of
+ * pages it contains.
+ *
+ * @return The size of the range.
+ *
+ * @hide
+ */
+ public int getSize() {
+ return mEnd - mStart + 1;
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index 2810d55..30f0c6a 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -105,6 +105,13 @@ public final class PrintAttributes implements Parcelable {
/**
* Gets the minimal margins. If the content does not fit
* these margins it will be clipped.
+ * <p>
+ * <strong>These margins are physically imposed by the printer and they
+ * are <em>not</em> rotated, i.e. they are the same for both portrait and
+ * landscape. For example, a printer may not be able to print in a stripe
+ * on both left and right sides of the page.
+ * </strong>
+ * </p>
*
* @return The margins or <code>null</code> if not set.
*/
@@ -115,6 +122,13 @@ public final class PrintAttributes implements Parcelable {
/**
* Sets the minimal margins. If the content does not fit
* these margins it will be clipped.
+ * <p>
+ * <strong>These margins are physically imposed by the printer and they
+ * are <em>not</em> rotated, i.e. they are the same for both portrait and
+ * landscape. For example, a printer may not be able to print in a stripe
+ * on both left and right sides of the page.
+ * </strong>
+ * </p>
*
* @param The margins.
*
@@ -193,14 +207,8 @@ public final class PrintAttributes implements Parcelable {
oldResolution.getHorizontalDpi());
attributes.setResolution(newResolution);
- // Rotate the physical margins.
- Margins oldMinMargins = getMinMargins();
- Margins newMinMargins = new Margins(
- oldMinMargins.getBottomMils(),
- oldMinMargins.getLeftMils(),
- oldMinMargins.getTopMils(),
- oldMinMargins.getRightMils());
- attributes.setMinMargins(newMinMargins);
+ // Do not rotate the physical margins.
+ attributes.setMinMargins(getMinMargins());
attributes.setColorMode(getColorMode());
@@ -236,14 +244,8 @@ public final class PrintAttributes implements Parcelable {
oldResolution.getHorizontalDpi());
attributes.setResolution(newResolution);
- // Rotate the physical margins.
- Margins oldMinMargins = getMinMargins();
- Margins newMargins = new Margins(
- oldMinMargins.getTopMils(),
- oldMinMargins.getRightMils(),
- oldMinMargins.getBottomMils(),
- oldMinMargins.getLeftMils());
- attributes.setMinMargins(newMargins);
+ // Do not rotate the physical margins.
+ attributes.setMinMargins(getMinMargins());
attributes.setColorMode(getColorMode());
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index 9361286..7ec838e 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -105,7 +105,7 @@ public final class PrintManager {
private static final String LOG_TAG = "PrintManager";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1;
diff --git a/core/jni/android/graphics/pdf/PdfRenderer.cpp b/core/jni/android/graphics/pdf/PdfRenderer.cpp
index 15de24a..303ddea 100644
--- a/core/jni/android/graphics/pdf/PdfRenderer.cpp
+++ b/core/jni/android/graphics/pdf/PdfRenderer.cpp
@@ -204,9 +204,13 @@ static void renderPageBitmap(FPDF_BITMAP bitmap, FPDF_PAGE page, int destLeft, i
// PDF's coordinate system origin is left-bottom while
// in graphics it is the top-left, so remap the origin.
matrix.Set(1, 0, 0, -1, 0, pPage->GetPageHeight());
- matrix.Scale(transform->getScaleX(), transform->getScaleY());
- matrix.Rotate(transform->getSkewX(), transform->getSkewY());
- matrix.Translate(transform->getTranslateX(), transform->getTranslateY());
+
+ SkScalar transformValues[6];
+ transform->asAffine(transformValues);
+
+ matrix.Concat(transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY],
+ transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY],
+ transformValues[SkMatrix::kATransX], transformValues[SkMatrix::kATransY]);
}
pageContext->AppendObjectList(pPage, &matrix);
@@ -251,6 +255,7 @@ static void nativeRenderPage(JNIEnv* env, jclass thiz, jlong documentPtr, jlong
renderPageBitmap(bitmap, page, destLeft, destTop, destRight,
destBottom, skMatrix, renderFlags);
+ skBitmap->notifyPixelsChanged();
skBitmap->unlockPixels();
}
diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java
index b63edce..b5d9729 100644
--- a/graphics/java/android/graphics/pdf/PdfRenderer.java
+++ b/graphics/java/android/graphics/pdf/PdfRenderer.java
@@ -70,6 +70,32 @@ import java.lang.annotation.RetentionPolicy;
* renderer.close();
* </pre>
*
+ * <h3>Print preview and print output</h3>
+ * <p>
+ * If you are using this class to rasterize a PDF for printing or show a print
+ * preview, it is recommended that you respect the following contract in order
+ * to provide a consistent user experience when seeing a preview and printing,
+ * i.e. the user sees a preview that is the same as the printout.
+ * </p>
+ * <ul>
+ * <li>
+ * Respect the property whether the document would like to be scaled for printing
+ * as per {@link #shouldScaleForPrinting()}.
+ * </li>
+ * <li>
+ * When scaling a document for printing the aspect ratio should be preserved.
+ * </li>
+ * <li>
+ * Do not inset the content with any margins from the {@link android.print.PrintAttributes}
+ * as the application is responsible to render it such that the margins are respected.
+ * </li>
+ * <li>
+ * If document page size is greater than the printed media size the content should
+ * be anchored to the upper left corner of the page for left-to-right locales and
+ * top right corner for right-to-left locales.
+ * </li>
+ * </ul>
+ *
* @see #close()
*/
public final class PdfRenderer implements AutoCloseable {
@@ -188,7 +214,6 @@ public final class PdfRenderer implements AutoCloseable {
private void doClose() {
if (mCurrentPage != null) {
mCurrentPage.close();
- mCurrentPage = null;
}
nativeClose(mNativeDocument);
try {
@@ -280,7 +305,7 @@ public final class PdfRenderer implements AutoCloseable {
* </p>
* <p>
* You may optionally specify a matrix to transform the content from page coordinates
- * which are in points (1/72") to bitmap coordintates which are in pixels. If this
+ * which are in points (1/72") to bitmap coordinates which are in pixels. If this
* matrix is not provided this method will apply a transformation that will fit the
* whole page to the destination clip if provided or the destination bitmap if no
* clip is provided.
@@ -375,6 +400,7 @@ public final class PdfRenderer implements AutoCloseable {
nativeClosePage(mNativePage);
mNativePage = 0;
mCloseGuard.close();
+ mCurrentPage = null;
}
private void throwIfClosed() {
diff --git a/packages/PrintSpooler/Android.mk b/packages/PrintSpooler/Android.mk
index 96592b4..a3a1f8c 100644
--- a/packages/PrintSpooler/Android.mk
+++ b/packages/PrintSpooler/Android.mk
@@ -24,6 +24,6 @@ LOCAL_PACKAGE_NAME := PrintSpooler
LOCAL_JAVA_LIBRARIES := framework-base
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 android-support-v7-recyclerview
include $(BUILD_PACKAGE)
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index 4c0bbb8..223013f 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -17,9 +17,9 @@
*/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.printspooler"
- android:versionName="1"
- android:versionCode="1">
+ package="com.android.printspooler"
+ android:versionName="1"
+ android:versionCode="1">
<!-- Allows an application to call APIs that give it access to all print jobs
on the device. Usually an app can access only the print jobs it created. -->
@@ -58,7 +58,7 @@
android:name=".ui.PrintActivity"
android:configChanges="orientation|screenSize"
android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE"
- android:theme="@android:style/Theme.DeviceDefault.NoActionBar">
+ android:theme="@style/PrintActivity">
<intent-filter>
<action android:name="android.print.PRINT_DIALOG" />
<category android:name="android.intent.category.DEFAULT" />
diff --git a/packages/PrintSpooler/res/layout/preview_page.xml b/packages/PrintSpooler/res/layout/preview_page.xml
new file mode 100644
index 0000000..0e314d1
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/preview_page.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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:id="@+id/preview_page"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/preview_page_margin"
+ android:orientation="vertical"
+ android:background="?android:attr/colorForeground">
+
+ <com.android.printspooler.widget.PageContentView
+ android:id="@+id/page_content"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ </com.android.printspooler.widget.PageContentView>
+
+ <RelativeLayout
+ android:id="@+id/page_footer"
+ android:layout_width="fill_parent"
+ android:layout_height="?android:attr/listPreferredItemHeightSmall"
+ android:background="@*android:color/material_grey_500"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/page_number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorPrimary">
+ </TextView>
+
+ <CheckBox
+ android:id="@+id/page_selector"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="8dip"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true">
+ </CheckBox>
+
+ </RelativeLayout>
+
+</LinearLayout>
diff --git a/packages/PrintSpooler/res/layout/print_activity.xml b/packages/PrintSpooler/res/layout/print_activity.xml
index 9715322..01cf9c1 100644
--- a/packages/PrintSpooler/res/layout/print_activity.xml
+++ b/packages/PrintSpooler/res/layout/print_activity.xml
@@ -14,24 +14,23 @@
limitations under the License.
-->
-<com.android.printspooler.widget.ContentView
+<com.android.printspooler.widget.PrintContentView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:printspooler="http://schemas.android.com/apk/res/com.android.printspooler"
android:id="@+id/options_content"
android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:visibility="invisible"
- android:background="?android:attr/colorForeground">
+ android:layout_height="fill_parent">
+
+ <!-- Destination -->
<FrameLayout
android:id="@+id/static_content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="16dip"
+ android:elevation="8dip"
android:background="?android:attr/colorForegroundInverse">
- <!-- Destination -->
-
<Spinner
android:id="@+id/destination_spinner"
android:layout_width="wrap_content"
@@ -51,6 +50,7 @@
android:paddingStart="16dip"
android:paddingEnd="16dip"
android:orientation="horizontal"
+ android:elevation="8dip"
android:background="?android:attr/colorForegroundInverse">
<TextView
@@ -69,7 +69,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
android:layout_marginStart="16dip"
- android:textAppearance="?android:attr/textAppearanceMedium">
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorPrimary">
</TextView>
<TextView
@@ -88,302 +89,317 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
android:layout_marginStart="16dip"
- android:textAppearance="?android:attr/textAppearanceMedium">
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorPrimary">
</TextView>
</LinearLayout>
- <FrameLayout
+ <!-- Print button -->
+
+ <ImageButton
+ android:id="@+id/print_button"
+ style="?android:attr/buttonStyleSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="16dip"
+ android:elevation="8dip"
+ android:background="@drawable/print_button">
+ </ImageButton>
+
+ <!-- Controls -->
+
+ <LinearLayout
android:id="@+id/dynamic_content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:paddingBottom="16dip">
+ android:orientation="vertical"
+ android:elevation="8dip"
+ android:background="?android:attr/colorForegroundInverse">
<LinearLayout
+ android:id="@+id/draggable_content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
- <LinearLayout
- android:id="@+id/draggable_content"
+ <com.android.printspooler.widget.PrintOptionsLayout
+ android:id="@+id/options_container"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:orientation="vertical">
+ printspooler:columnCount="@integer/print_option_column_count">
- <com.android.printspooler.widget.PrintOptionsLayout
- android:id="@+id/options_container"
- android:layout_width="fill_parent"
+ <LinearLayout
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="?android:attr/colorForegroundInverse"
- printspooler:columnCount="@integer/print_option_column_count">
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip"
+ android:orientation="vertical">
+
+ <!-- Copies -->
- <LinearLayout
+ <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="16dip"
- android:layout_marginEnd="16dip"
- android:orientation="vertical">
-
- <!-- Copies -->
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dip"
- android:layout_marginStart="12dip"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:labelFor="@+id/copies_edittext"
- android:text="@string/label_copies">
- </TextView>
-
- <view
- class="com.android.printspooler.widget.FirstFocusableEditText"
- android:id="@+id/copies_edittext"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- style="?android:attr/editTextStyle"
- android:inputType="numberDecimal">
- </view>
-
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginStart="12dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:labelFor="@+id/copies_edittext"
+ android:text="@string/label_copies">
+ </TextView>
+
+ <view
+ class="com.android.printspooler.widget.FirstFocusableEditText"
+ android:id="@+id/copies_edittext"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:layout_marginStart="16dip"
- android:layout_marginEnd="16dip"
- android:orientation="vertical">
-
- <!-- Paper size -->
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dip"
- android:layout_marginStart="12dip"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:labelFor="@+id/paper_size_spinner"
- android:text="@string/label_paper_size">
- </TextView>
-
- <Spinner
- android:id="@+id/paper_size_spinner"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- style="@style/PrintOptionSpinnerStyle">
- </Spinner>
-
- </LinearLayout>
-
- <LinearLayout
+ style="?android:attr/editTextStyle"
+ android:inputType="numberDecimal">
+ </view>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip"
+ android:orientation="vertical">
+
+ <!-- Paper size -->
+
+ <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="16dip"
- android:layout_marginEnd="16dip"
- android:orientation="vertical">
-
- <!-- Color -->
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dip"
- android:layout_marginStart="12dip"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:labelFor="@+id/color_spinner"
- android:text="@string/label_color">
- </TextView>
-
- <Spinner
- android:id="@+id/color_spinner"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- style="@style/PrintOptionSpinnerStyle">
- </Spinner>
-
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginStart="12dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:labelFor="@+id/paper_size_spinner"
+ android:text="@string/label_paper_size">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/paper_size_spinner"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:layout_marginStart="16dip"
- android:layout_marginEnd="16dip"
- android:orientation="vertical">
-
- <!-- Orientation -->
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dip"
- android:layout_marginStart="12dip"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:labelFor="@+id/orientation_spinner"
- android:text="@string/label_orientation">
- </TextView>
-
- <Spinner
- android:id="@+id/orientation_spinner"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- style="@style/PrintOptionSpinnerStyle">
- </Spinner>
-
- </LinearLayout>
-
- <LinearLayout
+ style="@style/PrintOptionSpinnerStyle">
+ </Spinner>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip"
+ android:orientation="vertical">
+
+ <!-- Color -->
+
+ <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="16dip"
- android:layout_marginEnd="16dip"
- android:orientation="vertical">
-
- <!-- Range options -->
-
- <TextView
- android:id="@+id/range_options_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dip"
- android:layout_marginStart="12dip"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:labelFor="@+id/range_options_spinner"
- android:text="@string/page_count_unknown">
- </TextView>
-
- <Spinner
- android:id="@+id/range_options_spinner"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- style="@style/PrintOptionSpinnerStyle">
- </Spinner>
-
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginStart="12dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:labelFor="@+id/color_spinner"
+ android:text="@string/label_color">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/color_spinner"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:layout_marginStart="16dip"
- android:layout_marginEnd="16dip"
- android:orientation="vertical">
-
- <!-- Pages -->
-
- <TextView
- android:id="@+id/page_range_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dip"
- android:layout_marginStart="12dip"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:text="@string/pages_range_example"
- android:labelFor="@+id/page_range_edittext"
- android:textAllCaps="false"
- android:visibility="visible">
- </TextView>
-
- <view
- class="com.android.printspooler.widget.FirstFocusableEditText"
- android:id="@+id/page_range_edittext"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|fill_horizontal"
- style="@style/PrintOptionEditTextStyle"
- android:visibility="visible"
- android:inputType="textNoSuggestions">
- </view>
-
- </LinearLayout>
-
- </com.android.printspooler.widget.PrintOptionsLayout>
-
- <!-- More options -->
+ style="@style/PrintOptionSpinnerStyle">
+ </Spinner>
+
+ </LinearLayout>
<LinearLayout
- android:id="@+id/more_options_container"
- android:layout_width="fill_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingStart="28dip"
- android:paddingEnd="28dip"
- android:orientation="vertical"
- android:visibility="visible"
- android:background="?android:attr/colorForegroundInverse">
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip"
+ android:orientation="vertical">
+
+ <!-- Orientation -->
- <ImageView
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginStart="12dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:labelFor="@+id/orientation_spinner"
+ android:text="@string/label_orientation">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/orientation_spinner"
android:layout_width="fill_parent"
- android:layout_height="1dip"
- android:layout_gravity="fill_horizontal"
- android:background="?android:attr/colorControlNormal"
- android:contentDescription="@null">
- </ImageView>
-
- <Button
- android:id="@+id/more_options_button"
- style="?android:attr/borderlessButtonStyle"
+ android:layout_height="wrap_content"
+ style="@style/PrintOptionSpinnerStyle">
+ </Spinner>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip"
+ android:orientation="vertical">
+
+ <!-- Range options -->
+
+ <TextView
+ android:id="@+id/range_options_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginStart="12dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:labelFor="@+id/range_options_spinner"
+ android:text="@string/page_count_unknown">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/range_options_spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:layout_gravity="fill_horizontal"
- android:text="@string/more_options_button"
- android:gravity="start|center_vertical"
- android:textAllCaps="false">
- </Button>
+ style="@style/PrintOptionSpinnerStyle">
+ </Spinner>
+
+ </LinearLayout>
- <ImageView
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip"
+ android:orientation="vertical">
+
+ <!-- Pages -->
+
+ <TextView
+ android:id="@+id/page_range_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginStart="12dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="@string/pages_range_example"
+ android:labelFor="@+id/page_range_edittext"
+ android:textAllCaps="false"
+ android:visibility="visible">
+ </TextView>
+
+ <view
+ class="com.android.printspooler.widget.FirstFocusableEditText"
+ android:id="@+id/page_range_edittext"
android:layout_width="fill_parent"
- android:layout_height="1dip"
- android:layout_gravity="fill_horizontal"
- android:background="?android:attr/colorControlNormal"
- android:contentDescription="@null">
- </ImageView>
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|fill_horizontal"
+ style="@style/PrintOptionEditTextStyle"
+ android:visibility="visible"
+ android:inputType="textNoSuggestions">
+ </view>
</LinearLayout>
- </LinearLayout>
+ </com.android.printspooler.widget.PrintOptionsLayout>
- <!-- Expand/collapse handle -->
+ <!-- More options -->
- <FrameLayout
- android:id="@+id/expand_collapse_handle"
+ <LinearLayout
+ android:id="@+id/more_options_container"
android:layout_width="fill_parent"
- android:layout_height="?android:attr/listPreferredItemHeightSmall"
- android:layout_marginBottom="28dip"
- android:background="?android:attr/colorForegroundInverse"
- android:elevation="12dip">
+ android:layout_height="wrap_content"
+ android:paddingStart="28dip"
+ android:paddingEnd="28dip"
+ android:orientation="vertical"
+ android:visibility="visible">
- <ImageButton
- android:id="@+id/expand_collapse_icon"
- android:layout_width="wrap_content"
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:layout_gravity="fill_horizontal"
+ android:background="?android:attr/colorControlNormal"
+ android:contentDescription="@null">
+ </ImageView>
+
+ <Button
+ android:id="@+id/more_options_button"
+ style="?android:attr/borderlessButtonStyle"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:background="@drawable/ic_expand_more">
- </ImageButton>
+ android:layout_gravity="fill_horizontal"
+ android:text="@string/more_options_button"
+ android:gravity="start|center_vertical"
+ android:textAllCaps="false">
+ </Button>
+
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:layout_gravity="fill_horizontal"
+ android:background="?android:attr/colorControlNormal"
+ android:contentDescription="@null">
+ </ImageView>
- </FrameLayout>
+ </LinearLayout>
</LinearLayout>
- <!-- Print button -->
+ <!-- Expand/collapse handle -->
- <ImageButton
- android:id="@+id/print_button"
- style="?android:attr/buttonStyleSmall"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="end|bottom"
- android:layout_marginEnd="16dip"
- android:elevation="12dip"
- android:background="@drawable/print_button"
- android:src="@*android:drawable/ic_print">
- </ImageButton>
+ <FrameLayout
+ android:id="@+id/expand_collapse_handle"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
- </FrameLayout>
+ <ImageView
+ android:id="@+id/expand_collapse_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginBottom="8dip"
+ android:layout_gravity="center"
+ android:background="@drawable/ic_expand_more">
+ </ImageView>
+ </FrameLayout>
- <FrameLayout
+ </LinearLayout>
+
+ <!-- Content -->
+
+ <com.android.printspooler.widget.EmbeddedContentContainer
android:id="@+id/embedded_content_container"
android:layout_width="fill_parent"
- android:layout_height="0dip"
- android:animateLayoutChanges="true">
- </FrameLayout>
+ android:layout_height="fill_parent"
+ android:animateLayoutChanges="true"
+ android:background="@color/print_preview_background_color"
+ android:gravity="center">
+
+ <!-- Error message added here -->
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/preview_content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ android:orientation="vertical">
+ </android.support.v7.widget.RecyclerView>
+
+ <!-- Scrim -->
+
+ <View
+ android:id="@+id/embedded_content_scrim"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ </View>
+
+ </com.android.printspooler.widget.EmbeddedContentContainer>
-</com.android.printspooler.widget.ContentView>
+</com.android.printspooler.widget.PrintContentView>
diff --git a/packages/PrintSpooler/res/layout/print_progress_fragment.xml b/packages/PrintSpooler/res/layout/print_progress_fragment.xml
index 212da9e..3b010f8 100644
--- a/packages/PrintSpooler/res/layout/print_progress_fragment.xml
+++ b/packages/PrintSpooler/res/layout/print_progress_fragment.xml
@@ -36,30 +36,13 @@
style="?android:attr/progressBarStyleHorizontal">
</ProgressBar>
- <FrameLayout
+ <TextView
+ android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeight"
- android:gravity="center"
- android:animateLayoutChanges="true">
-
- <TextView
- android:id="@+id/message"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceLargeInverse"
- android:text="@string/print_operation_canceling"
- android:visibility="gone">
- </TextView>
-
- <Button
- android:id="@+id/cancel_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@android:string/cancel">
- </Button>
-
- </FrameLayout>
+ android:textAppearance="?android:attr/textAppearanceLargeInverse"
+ android:text="@string/print_preparing_preview">
+ </TextView>
</LinearLayout>
diff --git a/packages/PrintSpooler/res/values-land/constants.xml b/packages/PrintSpooler/res/values-land/constants.xml
index 0db7513..e84a32b 100644
--- a/packages/PrintSpooler/res/values-land/constants.xml
+++ b/packages/PrintSpooler/res/values-land/constants.xml
@@ -18,6 +18,6 @@
<dimen name="printer_list_view_padding_start">48dip</dimen>
<dimen name="printer_list_view_padding_end">48dip</dimen>
- <integer name="print_option_column_count">3</integer>
+ <integer name="preview_page_per_row_count">2</integer>
</resources>
diff --git a/packages/PrintSpooler/res/values-sw600dp-land/constants.xml b/packages/PrintSpooler/res/values-sw600dp-land/constants.xml
index cacdf98..f60d8e4 100644
--- a/packages/PrintSpooler/res/values-sw600dp-land/constants.xml
+++ b/packages/PrintSpooler/res/values-sw600dp-land/constants.xml
@@ -17,5 +17,6 @@
<resources>
<integer name="print_option_column_count">6</integer>
+ <integer name="preview_page_per_row_count">4</integer>
</resources>
diff --git a/packages/PrintSpooler/res/values-sw600dp/constants.xml b/packages/PrintSpooler/res/values-sw600dp/constants.xml
index 14c099c..44de7bf 100644
--- a/packages/PrintSpooler/res/values-sw600dp/constants.xml
+++ b/packages/PrintSpooler/res/values-sw600dp/constants.xml
@@ -17,5 +17,6 @@
<resources>
<integer name="print_option_column_count">3</integer>
+ <integer name="preview_page_per_row_count">2</integer>
</resources>
diff --git a/packages/PrintSpooler/res/values/colors.xml b/packages/PrintSpooler/res/values/colors.xml
index fd6ae19..677fda7 100644
--- a/packages/PrintSpooler/res/values/colors.xml
+++ b/packages/PrintSpooler/res/values/colors.xml
@@ -18,4 +18,8 @@
<color name="print_button_tint_color">#EEFF41</color>
+ <color name="print_preview_scrim_color">#99000000</color>
+
+ <color name="print_preview_background_color">#F2F1F2</color>
+
</resources>
diff --git a/packages/PrintSpooler/res/values/constants.xml b/packages/PrintSpooler/res/values/constants.xml
index 9a2c14e..a19cd65 100644
--- a/packages/PrintSpooler/res/values/constants.xml
+++ b/packages/PrintSpooler/res/values/constants.xml
@@ -19,9 +19,9 @@
<integer name="page_option_value_all">0</integer>
<integer name="page_option_value_page_range">1</integer>
- <integer name="print_option_column_count">2</integer>
+ <integer name="preview_page_per_row_count">1</integer>
- <integer-array name="page_options_values" translatable="false">
+ <integer-array name="page_options_values">
<item>@integer/page_option_value_all</item>
<item>@integer/page_option_value_page_range</item>
</integer-array>
@@ -31,4 +31,11 @@
<dimen name="printer_list_view_padding_start">16dip</dimen>
<dimen name="printer_list_view_padding_end">16dip</dimen>
+ <dimen name="selected_page_elevation">6dip</dimen>
+ <dimen name="unselected_page_elevation">2dip</dimen>
+
+ <dimen name="preview_page_margin">8dip</dimen>
+
+ <dimen name="preview_list_padding">24dip</dimen>
+
</resources>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index d85529c..dd90bec 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -73,6 +73,11 @@
<!-- Title for the print dialog announced to the user for accessibility. Not shown in the UI. [CHAR LIMIT=none] -->
<string name="print_dialog">Print dialog</string>
+
+ <!-- Template for the message that shows the current page out of the total number of pages -->
+ <string name="current_page_template"><xliff:g id="current_page">%1$d</xliff:g>
+ /<xliff:g id="page_count">%2$d</xliff:g></string>
+
<!-- Select printer activity -->
<!-- Title for the share action bar menu item. [CHAR LIMIT=20] -->
@@ -203,17 +208,17 @@
<string name="print_write_error_message">Couldn\'t write to file</string>
<!-- Default message for an error while generating a print job. [CHAR LIMIT=50] -->
- <string name="print_error_default_message">Couldn\'t generate print job</string>
+ <string name="print_error_default_message">Sorry, that didn\'t work. Try again.</string>
<!-- Label for the retry button in the error message. [CHAR LIMIT=50] -->
<string name="print_error_retry">Retry</string>
- <!-- Message for the currently selected printer becoming unavailable. [CHAR LIMIT=50] -->
- <string name="print_error_printer_unavailable">Printer unavailable</string>
+ <!-- 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>
<!-- Long running operations -->
- <!-- Message for the cancel print operation UI while waiting for cancel to be performed. [CHAR LIMIT=50] -->
- <string name="print_operation_canceling">Cancelling\u2026</string>
+ <!-- Message long running operation when preparing print preview. [CHAR LIMIT=50] -->
+ <string name="print_preparing_preview">Preparing preview\u2026</string>
</resources>
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index 40bf725..e1e6c44 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -16,6 +16,13 @@
<resources>
+ <style name="PrintActivity" parent="@android:style/Theme.DeviceDefault.NoActionBar">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ </style>
+
<style name="SelectPrinterActivityTheme" parent="@android:style/Theme.DeviceDefault.Light">
<item name="android:actionBarStyle">@style/SelectPrinterActivityActionBarStyle</item>
</style>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/MutexFileProvider.java b/packages/PrintSpooler/src/com/android/printspooler/model/MutexFileProvider.java
new file mode 100644
index 0000000..1f48638
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/MutexFileProvider.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package com.android.printspooler.model;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * This class provides a shared file to several threads. Only one thread
+ * at a time can use the file. To acquire the file a thread has to
+ * request it in a blocking call to {@link #acquireFile(OnReleaseRequestCallback)}.
+ * The provided callback is optional and is used to notify the owning thread
+ * when another one wants to acquire the file. In case a release is requested
+ * the thread owning the file must release it as soon as possible. If no
+ * callback is provided a thread that acquires the file must release it
+ * as soon as possible, i.e. even if callback was provided the thread cannot
+ * have the file for less time.
+ */
+public final class MutexFileProvider {
+ private static final String LOG_TAG = "MutexFileProvider";
+
+ private static final boolean DEBUG = true;
+
+ private final Object mLock = new Object();
+
+ private final File mFile;
+
+ private Thread mOwnerThread;
+
+ private OnReleaseRequestCallback mOnReleaseRequestCallback;
+
+ public interface OnReleaseRequestCallback {
+ public void onReleaseRequested(File file);
+ }
+
+ public MutexFileProvider(File file) throws IOException {
+ mFile = file;
+ if (file.exists()) {
+ file.delete();
+ }
+ file.createNewFile();
+ }
+
+ public File acquireFile(OnReleaseRequestCallback callback) {
+ synchronized (mLock) {
+ // If this thread has the file, nothing to do.
+ if (mOwnerThread == Thread.currentThread()) {
+ return mFile;
+ }
+
+ // Another thread wants file ask for a release.
+ if (mOwnerThread != null && mOnReleaseRequestCallback != null) {
+ mOnReleaseRequestCallback.onReleaseRequested(mFile);
+ }
+
+ // Wait until the file is released.
+ while (mOwnerThread != null) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+
+ // Update the owner and the callback.
+ mOwnerThread = Thread.currentThread();
+ mOnReleaseRequestCallback = callback;
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Acquired file: " + mFile + " by thread: " + mOwnerThread);
+ }
+
+ return mFile;
+ }
+ }
+
+ public void releaseFile() {
+ synchronized (mLock) {
+ if (mOwnerThread != Thread.currentThread()) {
+ throw new IllegalStateException("Not acquired");
+ }
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Released file: " + mFile + " from thread: " + mOwnerThread);
+ }
+
+ // Update the owner and the callback.
+ mOwnerThread = null;
+ mOnReleaseRequestCallback = null;
+
+ mLock.notifyAll();
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index 929f0fc..d37ccc0 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -342,7 +342,7 @@ final class NotificationController {
printManager.cancelPrintJob(printJobId, PrintManager.APP_ID_ANY,
UserHandle.myUserId());
} catch (RemoteException re) {
- Log.i(LOG_TAG, "Error requestion print job cancellation", re);
+ Log.i(LOG_TAG, "Error requesting print job cancellation", re);
} finally {
wakeLock.release();
}
@@ -379,7 +379,7 @@ final class NotificationController {
printManager.restartPrintJob(printJobId, PrintManager.APP_ID_ANY,
UserHandle.myUserId());
} catch (RemoteException re) {
- Log.i(LOG_TAG, "Error requestion print job restart", re);
+ Log.i(LOG_TAG, "Error requesting print job restart", re);
} finally {
wakeLock.release();
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
new file mode 100644
index 0000000..63b4d96
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
@@ -0,0 +1,871 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package com.android.printspooler.model;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.pdf.PdfRenderer;
+import android.os.AsyncTask;
+import android.os.Debug;
+import android.os.ParcelFileDescriptor;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Margins;
+import android.print.PrintDocumentInfo;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.View;
+import dalvik.system.CloseGuard;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public final class PageContentRepository {
+ private static final String LOG_TAG = "PageContentRepository";
+
+ private static final boolean DEBUG = true;
+
+ private static final int INVALID_PAGE_INDEX = -1;
+
+ private static final int STATE_CLOSED = 0;
+ private static final int STATE_OPENED = 1;
+ private static final int STATE_DESTROYED = 2;
+
+ private static final int BYTES_PER_PIXEL = 4;
+
+ private static final int BYTES_PER_MEGABYTE = 1048576;
+
+ private static final int MILS_PER_INCH = 1000;
+ private static final int POINTS_IN_INCH = 72;
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private final ArrayMap<Integer, PageContentProvider> mPageContentProviders =
+ new ArrayMap<>();
+
+ private final AsyncRenderer mRenderer;
+
+ private RenderSpec mLastRenderSpec;
+
+ private int mScheduledPreloadFirstShownPage = INVALID_PAGE_INDEX;
+
+ private int mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX;
+
+ private int mState;
+
+ public interface OnPageContentAvailableCallback {
+ public void onPageContentAvailable(BitmapDrawable content);
+ }
+
+ public PageContentRepository(Context context) {
+ mRenderer = new AsyncRenderer(context);
+ mState = STATE_CLOSED;
+ if (DEBUG) {
+ Log.i(LOG_TAG, "STATE_CLOSED");
+ }
+ mCloseGuard.open("destroy");
+ }
+
+ public void open(ParcelFileDescriptor source, final Runnable callback) {
+ throwIfNotClosed();
+ mState = STATE_OPENED;
+ if (DEBUG) {
+ Log.i(LOG_TAG, "STATE_OPENED");
+ }
+ mRenderer.open(source, callback);
+ }
+
+ public void close(Runnable callback) {
+ throwIfNotOpened();
+ mState = STATE_CLOSED;
+ if (DEBUG) {
+ Log.i(LOG_TAG, "STATE_CLOSED");
+ }
+
+ mRenderer.close(callback);
+ }
+
+ public void destroy() {
+ throwIfNotClosed();
+ mState = STATE_DESTROYED;
+ if (DEBUG) {
+ Log.i(LOG_TAG, "STATE_DESTROYED");
+ }
+ throwIfNotClosed();
+ doDestroy();
+ }
+
+ public void startPreload(int firstShownPage, int lastShownPage) {
+ // If we do not have a render spec we have no clue what size the
+ // preloaded bitmaps should be, so just take a note for what to do.
+ if (mLastRenderSpec == null) {
+ mScheduledPreloadFirstShownPage = firstShownPage;
+ mScheduledPreloadLastShownPage = lastShownPage;
+ } else {
+ mRenderer.startPreload(firstShownPage, lastShownPage, mLastRenderSpec);
+ }
+ }
+
+ public void stopPreload() {
+ mRenderer.stopPreload();
+ }
+
+ public int getFilePageCount() {
+ return mRenderer.getPageCount();
+ }
+
+ public PageContentProvider peekPageContentProvider(int pageIndex) {
+ return mPageContentProviders.get(pageIndex);
+ }
+
+ public PageContentProvider acquirePageContentProvider(int pageIndex, View owner) {
+ throwIfDestroyed();
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Acquiring provider for page: " + pageIndex);
+ }
+
+ if (mPageContentProviders.get(pageIndex)!= null) {
+ throw new IllegalStateException("Already acquired for page: " + pageIndex);
+ }
+
+ PageContentProvider provider = new PageContentProvider(pageIndex, owner);
+
+ mPageContentProviders.put(pageIndex, provider);
+
+ return provider;
+ }
+
+ public void releasePageContentProvider(PageContentProvider provider) {
+ throwIfDestroyed();
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Releasing provider for page: " + provider.mPageIndex);
+ }
+
+ if (mPageContentProviders.remove(provider.mPageIndex) == null) {
+ throw new IllegalStateException("Not acquired");
+ }
+
+ provider.cancelLoad();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mState != STATE_DESTROYED) {
+ mCloseGuard.warnIfOpen();
+ doDestroy();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void doDestroy() {
+ mState = STATE_DESTROYED;
+ if (DEBUG) {
+ Log.i(LOG_TAG, "STATE_DESTROYED");
+ }
+ mRenderer.destroy();
+ }
+
+ private void throwIfNotOpened() {
+ if (mState != STATE_OPENED) {
+ throw new IllegalStateException("Not opened");
+ }
+ }
+
+ private void throwIfNotClosed() {
+ if (mState != STATE_CLOSED) {
+ throw new IllegalStateException("Not closed");
+ }
+ }
+
+ private void throwIfDestroyed() {
+ if (mState == STATE_DESTROYED) {
+ throw new IllegalStateException("Destroyed");
+ }
+ }
+
+ public final class PageContentProvider {
+ private final int mPageIndex;
+ private View mOwner;
+
+ public PageContentProvider(int pageIndex, View owner) {
+ mPageIndex = pageIndex;
+ mOwner = owner;
+ }
+
+ public View getOwner() {
+ return mOwner;
+ }
+
+ public int getPageIndex() {
+ return mPageIndex;
+ }
+
+ public void getPageContent(RenderSpec renderSpec, OnPageContentAvailableCallback callback) {
+ throwIfDestroyed();
+
+ mLastRenderSpec = renderSpec;
+
+ // We tired to preload but didn't know the bitmap size, now
+ // that we know let us do the work.
+ if (mScheduledPreloadFirstShownPage != INVALID_PAGE_INDEX
+ && mScheduledPreloadLastShownPage != INVALID_PAGE_INDEX) {
+ startPreload(mScheduledPreloadFirstShownPage, mScheduledPreloadLastShownPage);
+ mScheduledPreloadFirstShownPage = INVALID_PAGE_INDEX;
+ mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX;
+ }
+
+ if (mState == STATE_OPENED) {
+ mRenderer.renderPage(mPageIndex, renderSpec, callback);
+ } else {
+ mRenderer.getCachedPage(mPageIndex, renderSpec, callback);
+ }
+ }
+
+ void cancelLoad() {
+ throwIfDestroyed();
+
+ if (mState == STATE_OPENED) {
+ mRenderer.cancelRendering(mPageIndex);
+ }
+ }
+ }
+
+ private static final class PageContentLruCache {
+ private final LinkedHashMap<Integer, RenderedPage> mRenderedPages =
+ new LinkedHashMap<>();
+
+ private final int mMaxSizeInBytes;
+
+ private int mSizeInBytes;
+
+ public PageContentLruCache(int maxSizeInBytes) {
+ mMaxSizeInBytes = maxSizeInBytes;
+ }
+
+ public RenderedPage getRenderedPage(int pageIndex) {
+ return mRenderedPages.get(pageIndex);
+ }
+
+ public RenderedPage removeRenderedPage(int pageIndex) {
+ RenderedPage page = mRenderedPages.remove(pageIndex);
+ if (page != null) {
+ mSizeInBytes -= page.getSizeInBytes();
+ }
+ return page;
+ }
+
+ public RenderedPage putRenderedPage(int pageIndex, RenderedPage renderedPage) {
+ RenderedPage oldRenderedPage = mRenderedPages.remove(pageIndex);
+ if (oldRenderedPage != null) {
+ if (!oldRenderedPage.renderSpec.equals(renderedPage.renderSpec)) {
+ throw new IllegalStateException("Wrong page size");
+ }
+ } else {
+ final int contentSizeInBytes = renderedPage.getSizeInBytes();
+ if (mSizeInBytes + contentSizeInBytes > mMaxSizeInBytes) {
+ throw new IllegalStateException("Client didn't free space");
+ }
+
+ mSizeInBytes += contentSizeInBytes;
+ }
+ return mRenderedPages.put(pageIndex, renderedPage);
+ }
+
+ public void invalidate() {
+ for (Map.Entry<Integer, RenderedPage> entry : mRenderedPages.entrySet()) {
+ entry.getValue().state = RenderedPage.STATE_SCRAP;
+ }
+ }
+
+ public RenderedPage removeLeastNeeded() {
+ if (mRenderedPages.isEmpty()) {
+ return null;
+ }
+
+ // First try to remove a rendered page that holds invalidated
+ // or incomplete content, i.e. its render spec is null.
+ for (Map.Entry<Integer, RenderedPage> entry : mRenderedPages.entrySet()) {
+ RenderedPage renderedPage = entry.getValue();
+ if (renderedPage.state == RenderedPage.STATE_SCRAP) {
+ Integer pageIndex = entry.getKey();
+ mRenderedPages.remove(pageIndex);
+ mSizeInBytes -= renderedPage.getSizeInBytes();
+ return renderedPage;
+ }
+ }
+
+ // If all rendered pages contain rendered content, then use the oldest.
+ final int pageIndex = mRenderedPages.eldest().getKey();
+ RenderedPage renderedPage = mRenderedPages.remove(pageIndex);
+ mSizeInBytes -= renderedPage.getSizeInBytes();
+ return renderedPage;
+ }
+
+ public int getSizeInBytes() {
+ return mSizeInBytes;
+ }
+
+ public int getMaxSizeInBytes() {
+ return mMaxSizeInBytes;
+ }
+
+ public void clear() {
+ Iterator<Map.Entry<Integer, RenderedPage>> iterator =
+ mRenderedPages.entrySet().iterator();
+ while (iterator.hasNext()) {
+ iterator.next().getValue().recycle();
+ iterator.remove();
+ }
+ }
+ }
+
+ public static final class RenderSpec {
+ final int bitmapWidth;
+ final int bitmapHeight;
+ final MediaSize mediaSize;
+ final Margins minMargins;
+
+ public RenderSpec(int bitmapWidth, int bitmapHeight,
+ MediaSize mediaSize, Margins minMargins) {
+ this.bitmapWidth = bitmapWidth;
+ this.bitmapHeight = bitmapHeight;
+ this.mediaSize = mediaSize;
+ this.minMargins = minMargins;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ RenderSpec other = (RenderSpec) obj;
+ if (bitmapHeight != other.bitmapHeight) {
+ return false;
+ }
+ if (bitmapWidth != other.bitmapWidth) {
+ return false;
+ }
+ if (mediaSize != null) {
+ if (!mediaSize.equals(other.mediaSize)) {
+ return false;
+ }
+ } else if (other.mediaSize != null) {
+ return false;
+ }
+ if (minMargins != null) {
+ if (!minMargins.equals(other.minMargins)) {
+ return false;
+ }
+ } else if (other.minMargins != null) {
+ return false;
+ }
+ return true;
+ }
+
+ public boolean hasSameSize(RenderedPage page) {
+ Bitmap bitmap = page.content.getBitmap();
+ return bitmap.getWidth() == bitmapWidth
+ && bitmap.getHeight() == bitmapHeight;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = bitmapWidth;
+ result = 31 * result + bitmapHeight;
+ result = 31 * result + (mediaSize != null ? mediaSize.hashCode() : 0);
+ result = 31 * result + (minMargins != null ? minMargins.hashCode() : 0);
+ return result;
+ }
+ }
+
+ private static final class RenderedPage {
+ public static final int STATE_RENDERED = 0;
+ public static final int STATE_RENDERING = 1;
+ public static final int STATE_SCRAP = 2;
+
+ final BitmapDrawable content;
+ RenderSpec renderSpec;
+
+ int state = STATE_SCRAP;
+
+ RenderedPage(BitmapDrawable content) {
+ this.content = content;
+ }
+
+ public int getSizeInBytes() {
+ return content.getBitmap().getByteCount();
+ }
+
+ public void recycle() {
+ content.getBitmap().recycle();
+ }
+
+ public void erase() {
+ content.getBitmap().eraseColor(Color.WHITE);
+ }
+ }
+
+ private static int pointsFromMils(int mils) {
+ return (int) (((float) mils / MILS_PER_INCH) * POINTS_IN_INCH);
+ }
+
+ private static class AsyncRenderer {
+ private final Context mContext;
+
+ private final PageContentLruCache mPageContentCache;
+
+ private final ArrayMap<Integer, RenderPageTask> mPageToRenderTaskMap = new ArrayMap<>();
+
+ private int mPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
+
+ // Accessed only by the executor thread.
+ private PdfRenderer mRenderer;
+
+ public AsyncRenderer(Context context) {
+ mContext = context;
+
+ ActivityManager activityManager = (ActivityManager)
+ mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ final int cacheSizeInBytes = activityManager.getMemoryClass() * BYTES_PER_MEGABYTE / 4;
+ mPageContentCache = new PageContentLruCache(cacheSizeInBytes);
+ }
+
+ public void open(final ParcelFileDescriptor source, final Runnable callback) {
+ // Opening a new document invalidates the cache as it has pages
+ // from the last document. We keep the cache even when the document
+ // is closed to show pages while the other side is writing the new
+ // document.
+ mPageContentCache.invalidate();
+
+ new AsyncTask<Void, Void, Integer>() {
+ @Override
+ protected Integer doInBackground(Void... params) {
+ try {
+ mRenderer = new PdfRenderer(source);
+ return mRenderer.getPageCount();
+ } catch (IOException ioe) {
+ throw new IllegalStateException("Cannot open PDF document");
+ }
+ }
+
+ @Override
+ public void onPostExecute(Integer pageCount) {
+ mPageCount = pageCount;
+ if (callback != null) {
+ callback.run();
+ }
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
+ public void close(final Runnable callback) {
+ cancelAllRendering();
+
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ mRenderer.close();
+ return null;
+ }
+
+ @Override
+ public void onPostExecute(Void result) {
+ mPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
+ if (callback != null) {
+ callback.run();
+ }
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
+ public void destroy() {
+ mPageContentCache.invalidate();
+ mPageContentCache.clear();
+ }
+
+ public void startPreload(int firstShownPage, int lastShownPage, RenderSpec renderSpec) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Preloading pages around [" + firstShownPage
+ + "-" + lastShownPage + "]");
+ }
+
+ final int bitmapSizeInBytes = renderSpec.bitmapWidth * renderSpec.bitmapHeight
+ * BYTES_PER_PIXEL;
+ final int maxCachedPageCount = mPageContentCache.getMaxSizeInBytes()
+ / bitmapSizeInBytes;
+ final int halfPreloadCount = (maxCachedPageCount - (lastShownPage - firstShownPage)) /2;
+
+ final int excessFromStart;
+ if (firstShownPage - halfPreloadCount < 0) {
+ excessFromStart = halfPreloadCount - firstShownPage;
+ } else {
+ excessFromStart = 0;
+ }
+
+ final int excessFromEnd;
+ if (lastShownPage + halfPreloadCount >= mPageCount) {
+ excessFromEnd = (lastShownPage + halfPreloadCount) - mPageCount;
+ } else {
+ excessFromEnd = 0;
+ }
+
+ final int fromIndex = Math.max(firstShownPage - halfPreloadCount - excessFromEnd, 0);
+ final int toIndex = Math.min(lastShownPage + halfPreloadCount + excessFromStart,
+ mPageCount - 1);
+
+ for (int i = fromIndex; i <= toIndex; i++) {
+ renderPage(i, renderSpec, null);
+ }
+ }
+
+ public void stopPreload() {
+ final int taskCount = mPageToRenderTaskMap.size();
+ for (int i = 0; i < taskCount; i++) {
+ RenderPageTask task = mPageToRenderTaskMap.valueAt(i);
+ if (task.isPreload() && !task.isCancelled()) {
+ task.cancel(true);
+ }
+ }
+ }
+
+ public int getPageCount() {
+ return mPageCount;
+ }
+
+ public void getCachedPage(int pageIndex, RenderSpec renderSpec,
+ OnPageContentAvailableCallback callback) {
+ RenderedPage renderedPage = mPageContentCache.getRenderedPage(pageIndex);
+ if (renderedPage != null && renderedPage.state == RenderedPage.STATE_RENDERED
+ && renderedPage.renderSpec.equals(renderSpec)) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Cache hit for page: " + pageIndex);
+ }
+
+ // Announce if needed.
+ if (callback != null) {
+ callback.onPageContentAvailable(renderedPage.content);
+ }
+ }
+ }
+
+ public void renderPage(int pageIndex, RenderSpec renderSpec,
+ OnPageContentAvailableCallback callback) {
+ // First, check if we have a rendered page for this index.
+ RenderedPage renderedPage = mPageContentCache.getRenderedPage(pageIndex);
+ if (renderedPage != null && renderedPage.state == RenderedPage.STATE_RENDERED) {
+ // If we have rendered page with same constraints - done.
+ if (renderedPage.renderSpec.equals(renderSpec)) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Cache hit for page: " + pageIndex);
+ }
+
+ // Announce if needed.
+ if (callback != null) {
+ callback.onPageContentAvailable(renderedPage.content);
+ }
+ return;
+ } else {
+ // If the constraints changed, mark the page obsolete.
+ renderedPage.state = RenderedPage.STATE_SCRAP;
+ }
+ }
+
+ // Next, check if rendering this page is scheduled.
+ RenderPageTask renderTask = mPageToRenderTaskMap.get(pageIndex);
+ if (renderTask != null && !renderTask.isCancelled()) {
+ // If not rendered and constraints same....
+ if (renderTask.mRenderSpec.equals(renderSpec)) {
+ if (renderTask.mCallback != null) {
+ // If someone else is already waiting for this page - bad state.
+ if (callback != null && renderTask.mCallback != callback) {
+ throw new IllegalStateException("Page rendering not cancelled");
+ }
+ } else {
+ // No callback means we are preloading so just let the argument
+ // callback be attached to our work in progress.
+ renderTask.mCallback = callback;
+ }
+ return;
+ } else {
+ // If not rendered and constraints changed - cancel rendering.
+ renderTask.cancel(true);
+ }
+ }
+
+ // Oh well, we will have work to do...
+ renderTask = new RenderPageTask(pageIndex, renderSpec, callback);
+ mPageToRenderTaskMap.put(pageIndex, renderTask);
+ renderTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
+ public void cancelRendering(int pageIndex) {
+ RenderPageTask task = mPageToRenderTaskMap.get(pageIndex);
+ if (task != null && !task.isCancelled()) {
+ task.cancel(true);
+ }
+ }
+
+ private void cancelAllRendering() {
+ final int taskCount = mPageToRenderTaskMap.size();
+ for (int i = 0; i < taskCount; i++) {
+ RenderPageTask task = mPageToRenderTaskMap.valueAt(i);
+ if (!task.isCancelled()) {
+ task.cancel(true);
+ }
+ }
+ }
+
+ private final class RenderPageTask extends AsyncTask<Void, Void, RenderedPage> {
+ final int mPageIndex;
+ final RenderSpec mRenderSpec;
+ OnPageContentAvailableCallback mCallback;
+ RenderedPage mRenderedPage;
+
+ public RenderPageTask(int pageIndex, RenderSpec renderSpec,
+ OnPageContentAvailableCallback callback) {
+ mPageIndex = pageIndex;
+ mRenderSpec = renderSpec;
+ mCallback = callback;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ mRenderedPage = mPageContentCache.getRenderedPage(mPageIndex);
+ if (mRenderedPage != null && mRenderedPage.state == RenderedPage.STATE_RENDERED) {
+ throw new IllegalStateException("Trying to render a rendered page");
+ }
+
+ // Reuse bitmap for the page only if the right size.
+ if (mRenderedPage != null && !mRenderSpec.hasSameSize(mRenderedPage)) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Recycling bitmap for page: " + mPageIndex
+ + " with different size.");
+ }
+ mPageContentCache.removeRenderedPage(mPageIndex);
+ mRenderedPage.recycle();
+ mRenderedPage = null;
+ }
+
+ final int bitmapSizeInBytes = mRenderSpec.bitmapWidth
+ * mRenderSpec.bitmapHeight * BYTES_PER_PIXEL;
+
+ // Try to find a bitmap to reuse.
+ while (mRenderedPage == null) {
+
+ // Fill the cache greedily.
+ if (mPageContentCache.getSizeInBytes() <= 0
+ || mPageContentCache.getSizeInBytes() + bitmapSizeInBytes
+ <= mPageContentCache.getMaxSizeInBytes()) {
+ break;
+ }
+
+ RenderedPage renderedPage = mPageContentCache.removeLeastNeeded();
+
+ if (!mRenderSpec.hasSameSize(renderedPage)) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Recycling bitmap for page: " + mPageIndex
+ + " with different size.");
+ }
+ renderedPage.recycle();
+ continue;
+ }
+
+ mRenderedPage = renderedPage;
+ renderedPage.erase();
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Reused bitmap for page: " + mPageIndex + " cache size: "
+ + mPageContentCache.getSizeInBytes() + " bytes");
+ }
+
+ break;
+ }
+
+// if (mRenderedPage == null) {
+// final int bitmapSizeInBytes = mRenderSpec.bitmapWidth
+// * mRenderSpec.bitmapHeight * BYTES_PER_PIXEL;
+//
+// while (mPageContentCache.getSizeInBytes() > 0
+// && mPageContentCache.getSizeInBytes() + bitmapSizeInBytes
+// > mPageContentCache.getMaxSizeInBytes()) {
+// RenderedPage renderedPage = mPageContentCache.removeLeastNeeded();
+//
+// // If not the right size - recycle and keep freeing space.
+// Bitmap bitmap = renderedPage.content.getBitmap();
+// if (!mRenderSpec.hasSameSize(bitmap.getWidth(), bitmap.getHeight())) {
+// if (DEBUG) {
+// Log.i(LOG_TAG, "Recycling bitmap for page: " + mPageIndex
+// + " with different size.");
+// }
+// bitmap.recycle();
+// continue;
+// }
+//
+// mRenderedPage = renderedPage;
+// bitmap.eraseColor(Color.WHITE);
+//
+// if (DEBUG) {
+// Log.i(LOG_TAG, "Reused bitmap for page: " + mPageIndex + " cache size: "
+// + mPageContentCache.getSizeInBytes() + " bytes");
+// }
+// }
+// }
+
+ if (mRenderedPage == null) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Created bitmap for page: " + mPageIndex + " cache size: "
+ + mPageContentCache.getSizeInBytes() + " bytes");
+ }
+ Bitmap bitmap = Bitmap.createBitmap(mRenderSpec.bitmapWidth,
+ mRenderSpec.bitmapHeight, Bitmap.Config.ARGB_8888);
+ bitmap.eraseColor(Color.WHITE);
+ BitmapDrawable content = new BitmapDrawable(mContext.getResources(), bitmap);
+ mRenderedPage = new RenderedPage(content);
+ }
+
+ mRenderedPage.renderSpec = mRenderSpec;
+ mRenderedPage.state = RenderedPage.STATE_RENDERING;
+
+ mPageContentCache.putRenderedPage(mPageIndex, mRenderedPage);
+ }
+
+ @Override
+ protected RenderedPage doInBackground(Void... params) {
+ if (isCancelled()) {
+ return mRenderedPage;
+ }
+
+ PdfRenderer.Page page = mRenderer.openPage(mPageIndex);
+
+ if (isCancelled()) {
+ page.close();
+ return mRenderedPage;
+ }
+
+ Bitmap bitmap = mRenderedPage.content.getBitmap();
+
+ final int srcWidthPts = page.getWidth();
+ final int srcHeightPts = page.getHeight();
+
+ final int dstWidthPts = pointsFromMils(mRenderSpec.mediaSize.getWidthMils());
+ final int dstHeightPts = pointsFromMils(mRenderSpec.mediaSize.getHeightMils());
+
+ final boolean scaleContent = mRenderer.shouldScaleForPrinting();
+ final boolean contentLandscape = !mRenderSpec.mediaSize.isPortrait();
+
+ final float displayScale;
+ Matrix matrix = new Matrix();
+
+ if (scaleContent) {
+ displayScale = Math.min((float) bitmap.getWidth() / srcWidthPts,
+ (float) bitmap.getHeight() / srcHeightPts);
+ } else {
+ if (contentLandscape) {
+ displayScale = (float) bitmap.getHeight() / dstHeightPts;
+ } else {
+ displayScale = (float) bitmap.getWidth() / dstWidthPts;
+ }
+ }
+ matrix.postScale(displayScale, displayScale);
+
+ Configuration configuration = mContext.getResources().getConfiguration();
+ if (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ matrix.postTranslate(bitmap.getWidth() - srcWidthPts * displayScale, 0);
+ }
+
+ final int paddingLeftPts = pointsFromMils(mRenderSpec.minMargins.getLeftMils());
+ final int paddingTopPts = pointsFromMils(mRenderSpec.minMargins.getTopMils());
+ final int paddingRightPts = pointsFromMils(mRenderSpec.minMargins.getRightMils());
+ final int paddingBottomPts = pointsFromMils(mRenderSpec.minMargins.getBottomMils());
+
+ Rect clip = new Rect();
+ clip.left = (int) (paddingLeftPts * displayScale);
+ clip.top = (int) (paddingTopPts * displayScale);
+ clip.right = (int) (bitmap.getWidth() - paddingRightPts * displayScale);
+ clip.bottom = (int) (bitmap.getHeight() - paddingBottomPts * displayScale);
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Rendering page:" + mPageIndex + " of " + mPageCount);
+ }
+
+ page.render(bitmap, clip, matrix, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
+
+ page.close();
+
+ return mRenderedPage;
+ }
+
+ @Override
+ public void onPostExecute(RenderedPage renderedPage) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Completed rendering page: " + mPageIndex);
+ }
+
+ // This task is done.
+ mPageToRenderTaskMap.remove(mPageIndex);
+
+ // Take a note that the content is rendered.
+ renderedPage.state = RenderedPage.STATE_RENDERED;
+
+ // Announce success if needed.
+ if (mCallback != null) {
+ mCallback.onPageContentAvailable(renderedPage.content);
+ }
+ }
+
+ @Override
+ protected void onCancelled(RenderedPage renderedPage) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Cancelled rendering page: " + mPageIndex);
+ }
+
+ // This task is done.
+ mPageToRenderTaskMap.remove(mPageIndex);
+
+ // If canceled before on pre-execute.
+ if (renderedPage == null) {
+ return;
+ }
+
+ // Take a note that the content is not rendered.
+ renderedPage.state = RenderedPage.STATE_SCRAP;
+ }
+
+ public boolean isPreload() {
+ return mCallback == null;
+ }
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
index e70c361..9351078 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
@@ -55,7 +55,7 @@ import java.util.Arrays;
public final class RemotePrintDocument {
private static final String LOG_TAG = "RemotePrintDocument";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
private static final int STATE_INITIAL = 0;
private static final int STATE_STARTED = 1;
@@ -67,10 +67,6 @@ public final class RemotePrintDocument {
private static final int STATE_CANCELED = 7;
private static final int STATE_DESTROYED = 8;
- private static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {
- PageRange.ALL_PAGES
- };
-
private final Context mContext;
private final RemotePrintDocumentInfo mDocumentInfo;
@@ -93,18 +89,23 @@ public final class RemotePrintDocument {
// do nothing. However, if there is no next command we may need to
// ask for some pages given we do not already have them or we do
// but the content has changed.
- LayoutCommand layoutCommand = (LayoutCommand) mCurrentCommand;
if (mNextCommand == null) {
- if (layoutCommand.isDocumentChanged() || !PageRangeUtils.contains(
- mDocumentInfo.writtenPages, mUpdateSpec.pages)) {
+ if (mUpdateSpec.pages != null && (mDocumentInfo.changed
+ || (mDocumentInfo.info.getPageCount()
+ != PrintDocumentInfo.PAGE_COUNT_UNKNOWN
+ && !PageRangeUtils.contains(mDocumentInfo.writtenPages,
+ mUpdateSpec.pages, mDocumentInfo.info.getPageCount())))) {
mNextCommand = new WriteCommand(mContext, mLooper,
mPrintDocumentAdapter, mDocumentInfo,
mDocumentInfo.info.getPageCount(), mUpdateSpec.pages,
- mDocumentInfo.file, mCommandResultCallback);
+ mDocumentInfo.fileProvider, mCommandResultCallback);
} else {
- // If we have the requested pages just update that ones to be printed.
- mDocumentInfo.printedPages = computePrintedPages(mUpdateSpec.pages,
- mDocumentInfo.writtenPages, mDocumentInfo.info.getPageCount());
+ if (mUpdateSpec.pages != null) {
+ // If we have the requested pages, update which ones to be printed.
+ mDocumentInfo.printedPages = PageRangeUtils.computePrintedPages(
+ mUpdateSpec.pages, mDocumentInfo.writtenPages,
+ mDocumentInfo.info.getPageCount());
+ }
// Notify we are done.
notifyUpdateCompleted();
}
@@ -154,13 +155,14 @@ public final class RemotePrintDocument {
}
public RemotePrintDocument(Context context, IPrintDocumentAdapter adapter,
- File file, DocumentObserver destroyListener, UpdateResultCallbacks callbacks) {
+ MutexFileProvider fileProvider, DocumentObserver destroyListener,
+ UpdateResultCallbacks callbacks) {
mPrintDocumentAdapter = adapter;
mLooper = context.getMainLooper();
mContext = context;
mDocumentObserver = destroyListener;
mDocumentInfo = new RemotePrintDocumentInfo();
- mDocumentInfo.file = file;
+ mDocumentInfo.fileProvider = fileProvider;
mUpdateCallbacks = callbacks;
connectToRemoteDocument();
}
@@ -219,7 +221,8 @@ public final class RemotePrintDocument {
// If no layout in progress and we don't have all pages - schedule a write.
} else if ((!(mCurrentCommand instanceof LayoutCommand)
|| (!mCurrentCommand.isPending() && !mCurrentCommand.isRunning()))
- && !PageRangeUtils.contains(mUpdateSpec.pages, pages)) {
+ && pages != null && !PageRangeUtils.contains(mUpdateSpec.pages, pages,
+ mDocumentInfo.info.getPageCount())) {
willUpdate = true;
// Cancel the current write as a new one is to be scheduled.
@@ -231,7 +234,7 @@ public final class RemotePrintDocument {
// Schedule a write command.
AsyncCommand command = new WriteCommand(mContext, mLooper, mPrintDocumentAdapter,
mDocumentInfo, mDocumentInfo.info.getPageCount(), pages,
- mDocumentInfo.file, mCommandResultCallback);
+ mDocumentInfo.fileProvider, mCommandResultCallback);
scheduleCommand(command);
mState = STATE_UPDATING;
@@ -325,10 +328,12 @@ public final class RemotePrintDocument {
}
public void writeContent(ContentResolver contentResolver, Uri uri) {
+ File file = null;
InputStream in = null;
OutputStream out = null;
try {
- in = new FileInputStream(mDocumentInfo.file);
+ file = mDocumentInfo.fileProvider.acquireFile(null);
+ in = new FileInputStream(file);
out = contentResolver.openOutputStream(uri);
final byte[] buffer = new byte[8192];
while (true) {
@@ -343,6 +348,9 @@ public final class RemotePrintDocument {
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
+ if (file != null) {
+ mDocumentInfo.fileProvider.releaseFile();
+ }
}
}
@@ -453,42 +461,6 @@ public final class RemotePrintDocument {
}
}
- private static PageRange[] computePrintedPages(PageRange[] requestedPages,
- PageRange[] writtenPages, int pageCount) {
- // Adjust the print job pages based on what was requested and written.
- // The cases are ordered in the most expected to the least expected.
- if (Arrays.equals(writtenPages, requestedPages)) {
- // We got a document with exactly the pages we wanted. Hence,
- // the printer has to print all pages in the data.
- return ALL_PAGES_ARRAY;
- } else if (Arrays.equals(writtenPages, ALL_PAGES_ARRAY)) {
- // We requested specific pages but got all of them. Hence,
- // the printer has to print only the requested pages.
- return requestedPages;
- } else if (PageRangeUtils.contains(writtenPages, requestedPages)) {
- // We requested specific pages and got more but not all pages.
- // Hence, we have to offset appropriately the printed pages to
- // be based off the start of the written ones instead of zero.
- // The written pages are always non-null and not empty.
- final int offset = -writtenPages[0].getStart();
- PageRangeUtils.offset(requestedPages, offset);
- return requestedPages;
- } else if (Arrays.equals(requestedPages, ALL_PAGES_ARRAY)
- && isAllPages(writtenPages, pageCount)) {
- // We requested all pages via the special constant and got all
- // of them as an explicit enumeration. Hence, the printer has
- // to print only the requested pages.
- return ALL_PAGES_ARRAY;
- }
-
- return null;
- }
-
- private static boolean isAllPages(PageRange[] pageRanges, int pageCount) {
- return pageRanges.length > 0 && pageRanges[0].getStart() == 0
- && pageRanges[pageRanges.length - 1].getEnd() == pageCount - 1;
- }
-
static final class UpdateSpec {
final PrintAttributes attributes = new PrintAttributes.Builder().build();
boolean preview;
@@ -498,7 +470,7 @@ public final class RemotePrintDocument {
PageRange[] pages) {
this.attributes.copyFrom(attributes);
this.preview = preview;
- this.pages = Arrays.copyOf(pages, pages.length);
+ this.pages = (pages != null) ? Arrays.copyOf(pages, pages.length) : null;
}
public void reset() {
@@ -518,7 +490,10 @@ public final class RemotePrintDocument {
public PrintDocumentInfo info;
public PageRange[] printedPages;
public PageRange[] writtenPages;
- public File file;
+ public MutexFileProvider fileProvider;
+ public boolean changed;
+ public boolean updated;
+ public boolean laidout;
}
private interface CommandDoneCallback {
@@ -647,8 +622,6 @@ public final class RemotePrintDocument {
private final Handler mHandler;
- private boolean mDocumentChanged;
-
public LayoutCommand(Looper looper, IPrintDocumentAdapter adapter,
RemotePrintDocumentInfo document, PrintAttributes oldAttributes,
PrintAttributes newAttributes, boolean preview, CommandDoneCallback callback) {
@@ -660,10 +633,6 @@ public final class RemotePrintDocument {
mMetadata.putBoolean(PrintDocumentAdapter.EXTRA_PRINT_PREVIEW, preview);
}
- public boolean isDocumentChanged() {
- return mDocumentChanged;
- }
-
@Override
public void run() {
running();
@@ -672,6 +641,7 @@ public final class RemotePrintDocument {
if (DEBUG) {
Log.i(LOG_TAG, "[PERFORMING] layout");
}
+ mDocument.changed = false;
mAdapter.layout(mOldAttributes, mNewAttributes, mRemoteResultCallback,
mMetadata, mSequence);
} catch (RemoteException re) {
@@ -720,12 +690,13 @@ public final class RemotePrintDocument {
// we will request them again with the new content.
mDocument.writtenPages = null;
mDocument.printedPages = null;
- mDocumentChanged = true;
+ mDocument.changed = true;
}
// Update the document with data from the layout pass.
mDocument.attributes = mNewAttributes;
mDocument.metadata = mMetadata;
+ mDocument.laidout = true;
mDocument.info = info;
// Release the remote cancellation interface.
@@ -744,6 +715,8 @@ public final class RemotePrintDocument {
Log.i(LOG_TAG, "[CALLBACK] onLayoutFailed");
}
+ mDocument.laidout = false;
+
failed(error);
// Release the remote cancellation interface.
@@ -877,7 +850,7 @@ public final class RemotePrintDocument {
private static final class WriteCommand extends AsyncCommand {
private final int mPageCount;
private final PageRange[] mPages;
- private final File mContentFile;
+ private final MutexFileProvider mFileProvider;
private final IWriteResultCallback mRemoteResultCallback;
private final CommandDoneCallback mDoneCallback;
@@ -887,14 +860,14 @@ public final class RemotePrintDocument {
public WriteCommand(Context context, Looper looper, IPrintDocumentAdapter adapter,
RemotePrintDocumentInfo document, int pageCount, PageRange[] pages,
- File contentFile, CommandDoneCallback callback) {
+ MutexFileProvider fileProvider, CommandDoneCallback callback) {
super(adapter, document, callback);
mContext = context;
mHandler = new WriteHandler(looper);
mRemoteResultCallback = new WriteResultCallback(mHandler);
mPageCount = pageCount;
mPages = Arrays.copyOf(pages, pages.length);
- mContentFile = contentFile;
+ mFileProvider = fileProvider;
mDoneCallback = callback;
}
@@ -909,17 +882,19 @@ public final class RemotePrintDocument {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
+ File file = null;
InputStream in = null;
OutputStream out = null;
ParcelFileDescriptor source = null;
ParcelFileDescriptor sink = null;
try {
+ file = mFileProvider.acquireFile(null);
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
source = pipe[0];
sink = pipe[1];
in = new FileInputStream(source.getFileDescriptor());
- out = new FileOutputStream(mContentFile);
+ out = new FileOutputStream(file);
// Async call to initiate the other process writing the data.
if (DEBUG) {
@@ -947,6 +922,9 @@ public final class RemotePrintDocument {
IoUtils.closeQuietly(out);
IoUtils.closeQuietly(sink);
IoUtils.closeQuietly(source);
+ if (file != null) {
+ mFileProvider.releaseFile();
+ }
}
return null;
}
@@ -984,7 +962,8 @@ public final class RemotePrintDocument {
}
PageRange[] writtenPages = PageRangeUtils.normalize(pages);
- PageRange[] printedPages = computePrintedPages(mPages, writtenPages, mPageCount);
+ PageRange[] printedPages = PageRangeUtils.computePrintedPages(
+ mPages, writtenPages, mPageCount);
// Handle if we got invalid pages
if (printedPages != null) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
new file mode 100644
index 0000000..09ce4e1
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -0,0 +1,783 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package com.android.printspooler.ui;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.print.PageRange;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Margins;
+import android.print.PrintDocumentInfo;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.CheckBox;
+import android.widget.TextView;
+import com.android.printspooler.R;
+import com.android.printspooler.model.PageContentRepository;
+import com.android.printspooler.model.PageContentRepository.PageContentProvider;
+import com.android.printspooler.util.PageRangeUtils;
+import com.android.printspooler.widget.PageContentView;
+import dalvik.system.CloseGuard;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class represents the adapter for the pages in the print preview list.
+ */
+public final class PageAdapter extends Adapter {
+ private static final String LOG_TAG = "PageAdapter";
+
+ private static final int MAX_PREVIEW_PAGES_BATCH = 50;
+
+ private static final boolean DEBUG = true;
+
+ private static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {
+ PageRange.ALL_PAGES
+ };
+
+ private static final int INVALID_PAGE_INDEX = -1;
+
+ private static final int STATE_CLOSED = 0;
+ private static final int STATE_OPENED = 1;
+ private static final int STATE_DESTROYED = 2;
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private final SparseArray<Void> mBoundPagesInAdapter = new SparseArray<>();
+
+ private final SparseArray<Void> mConfirmedPagesInDocument = new SparseArray<>();
+
+ private final PageClickListener mPageClickListener = new PageClickListener();
+
+ private final LayoutInflater mLayoutInflater;
+
+ private final Context mContext;
+
+ private final ContentUpdateRequestCallback mContentUpdateRequestCallback;
+
+ private final PageContentRepository mPageContentRepository;
+
+ private final PreviewArea mPreviewArea;
+
+ private int mDocumentPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
+
+ private int mSelectedPageCount;
+
+ // Which document pages to be written.
+ private PageRange[] mRequestedPages;
+
+ // Pages written in the current file.
+ private PageRange[] mWrittenPages;
+
+ // Pages the user selected in the UI.
+ private PageRange[] mSelectedPages;
+
+ private float mSelectedPageElevation;
+
+ private float mUnselectedPageElevation;
+
+ private int mPreviewPageMargin;
+
+ private int mPreviewListPadding;
+
+ private int mFooterHeight;
+
+ private int mColumnCount;
+
+ private MediaSize mMediaSize;
+
+ private Margins mMinMargins;
+
+ private int mState;
+
+ private int mPageContentWidth;
+
+ private int mPageContentHeight;
+
+ public interface ContentUpdateRequestCallback {
+ public void requestContentUpdate();
+ }
+
+ public interface PreviewArea {
+ public int getWidth();
+ public int getHeight();
+ public void setColumnCount(int columnCount);
+ public void setPadding(int left, int top, int right, int bottom);
+ }
+
+ public PageAdapter(Context context, ContentUpdateRequestCallback updateRequestCallback,
+ PreviewArea previewArea) {
+ mContext = context;
+ mContentUpdateRequestCallback = updateRequestCallback;
+ mLayoutInflater = (LayoutInflater) context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ mPageContentRepository = new PageContentRepository(context);
+
+ mSelectedPageElevation = mContext.getResources().getDimension(
+ R.dimen.selected_page_elevation);
+ mUnselectedPageElevation = mContext.getResources().getDimension(
+ R.dimen.unselected_page_elevation);
+
+ mPreviewPageMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.preview_page_margin);
+
+ mPreviewListPadding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.preview_list_padding);
+
+ mColumnCount = mContext.getResources().getInteger(
+ R.integer.preview_page_per_row_count);
+
+ TypedValue outValue = new TypedValue();
+ mContext.getTheme().resolveAttribute(
+ com.android.internal.R.attr.listPreferredItemHeightSmall, outValue, true);
+ mFooterHeight = TypedValue.complexToDimensionPixelSize(outValue.data,
+ mContext.getResources().getDisplayMetrics());
+
+ mPreviewArea = previewArea;
+
+ mCloseGuard.open("destroy");
+
+ setHasStableIds(true);
+
+ mState = STATE_CLOSED;
+ if (DEBUG) {
+ Log.i(LOG_TAG, "STATE_CLOSED");
+ }
+ }
+
+ public void onOrientationChanged() {
+ mColumnCount = mContext.getResources().getInteger(
+ R.integer.preview_page_per_row_count);
+ }
+
+ public boolean isOpened() {
+ return mState == STATE_OPENED;
+ }
+
+ public int getFilePageCount() {
+ return mPageContentRepository.getFilePageCount();
+ }
+
+ public void open(ParcelFileDescriptor source, Runnable callback) {
+ throwIfNotClosed();
+ mState = STATE_OPENED;
+ if (DEBUG) {
+ Log.i(LOG_TAG, "STATE_OPENED");
+ }
+ mPageContentRepository.open(source, callback);
+ }
+
+ public void update(PageRange[] writtenPages, PageRange[] selectedPages,
+ int documentPageCount, MediaSize mediaSize, Margins minMargins) {
+ boolean documentChanged = false;
+ boolean updatePreviewAreaAndPageSize = false;
+
+ // If the app does not tell how many pages are in the document we cannot
+ // optimize and ask for all pages whose count we get from the renderer.
+ if (documentPageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
+ if (writtenPages == null) {
+ // If we already requested all pages, just wait.
+ if (!Arrays.equals(ALL_PAGES_ARRAY, mRequestedPages)) {
+ mRequestedPages = ALL_PAGES_ARRAY;
+ mContentUpdateRequestCallback.requestContentUpdate();
+ }
+ return;
+ } else {
+ documentPageCount = mPageContentRepository.getFilePageCount();
+ if (documentPageCount <= 0) {
+ return;
+ }
+ }
+ }
+
+ if (!Arrays.equals(mSelectedPages, selectedPages)) {
+ mSelectedPages = selectedPages;
+ mSelectedPageCount = PageRangeUtils.getNormalizedPageCount(
+ mSelectedPages, documentPageCount);
+ setConfirmedPages(mSelectedPages, documentPageCount);
+ updatePreviewAreaAndPageSize = true;
+ documentChanged = true;
+ }
+
+ if (mDocumentPageCount != documentPageCount) {
+ mDocumentPageCount = documentPageCount;
+ documentChanged = true;
+ }
+
+ if (mMediaSize == null || !mMediaSize.equals(mediaSize)) {
+ mMediaSize = mediaSize;
+ updatePreviewAreaAndPageSize = true;
+ documentChanged = true;
+ }
+
+ if (mMinMargins == null || !mMinMargins.equals(minMargins)) {
+ mMinMargins = minMargins;
+ updatePreviewAreaAndPageSize = true;
+ documentChanged = true;
+ }
+
+ // If *all pages* is selected we need to convert that to absolute
+ // range as we will be checking if some pages are written or not.
+ if (writtenPages != null) {
+ // If we get all pages, this means all pages that we requested.
+ if (PageRangeUtils.isAllPages(writtenPages)) {
+ writtenPages = mRequestedPages;
+ }
+ if (!Arrays.equals(mWrittenPages, writtenPages)) {
+ // TODO: Do a surgical invalidation of only written pages changed.
+ mWrittenPages = writtenPages;
+ documentChanged = true;
+ }
+ }
+
+ if (updatePreviewAreaAndPageSize) {
+ updatePreviewAreaAndPageSize();
+ }
+
+ if (documentChanged) {
+ notifyDataSetChanged();
+ }
+ }
+
+ public void close(Runnable callback) {
+ throwIfNotOpened();
+ mState = STATE_CLOSED;
+ if (DEBUG) {
+ Log.i(LOG_TAG, "STATE_CLOSED");
+ }
+ mPageContentRepository.close(callback);
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View page = mLayoutInflater.inflate(R.layout.preview_page, parent, false);
+ ViewHolder holder = new MyViewHolder(page);
+ holder.setIsRecyclable(true);
+ return holder;
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Binding holder: " + holder + " with id: " + getItemId(position)
+ + " for position: " + position);
+ }
+
+ final int pageCount = getItemCount();
+ MyViewHolder myHolder = (MyViewHolder) holder;
+
+ View page = holder.itemView;
+ if (pageCount > 1) {
+ page.setOnClickListener(mPageClickListener);
+ } else {
+ page.setOnClickListener(null);
+ }
+ page.setTag(holder);
+
+ myHolder.mPageInAdapter = position;
+
+ final int pageInDocument = computePageIndexInDocument(position);
+ final int pageIndexInFile = computePageIndexInFile(pageInDocument);
+
+ PageContentView content = (PageContentView) page.findViewById(R.id.page_content);
+
+ LayoutParams params = content.getLayoutParams();
+ params.width = mPageContentWidth;
+ params.height = mPageContentHeight;
+
+ PageContentProvider provider = content.getPageContentProvider();
+
+ if (pageIndexInFile != INVALID_PAGE_INDEX) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Binding provider:"
+ + " pageIndexInAdapter: " + position
+ + ", pageInDocument: " + pageInDocument
+ + ", pageIndexInFile: " + pageIndexInFile);
+ }
+
+ // OK, there are bugs in recycler view which tries to bind views
+ // without recycling them which would give us a chane to clean up.
+ PageContentProvider boundProvider = mPageContentRepository
+ .peekPageContentProvider(pageIndexInFile);
+ if (boundProvider != null) {
+ PageContentView owner = (PageContentView) boundProvider.getOwner();
+ owner.init(null, mMediaSize, mMinMargins);
+ mPageContentRepository.releasePageContentProvider(boundProvider);
+ }
+
+ provider = mPageContentRepository.acquirePageContentProvider(
+ pageIndexInFile, content);
+ mBoundPagesInAdapter.put(position, null);
+ } else {
+ onSelectedPageNotInFile(pageInDocument);
+ }
+ content.init(provider, mMediaSize, mMinMargins);
+
+
+ CheckBox checkbox = (CheckBox) page.findViewById(R.id.page_selector);
+ checkbox.setTag(myHolder);
+ if (pageCount > 1) {
+ checkbox.setOnClickListener(mPageClickListener);
+ checkbox.setVisibility(View.VISIBLE);
+ } else {
+ checkbox.setOnClickListener(null);
+ checkbox.setVisibility(View.GONE);
+ }
+
+ if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) >= 0) {
+ checkbox.setChecked(true);
+ page.setTranslationZ(mSelectedPageElevation);
+ } else {
+ checkbox.setChecked(false);
+ page.setTranslationZ(mUnselectedPageElevation);
+ }
+
+ TextView pageNumberView = (TextView) page.findViewById(R.id.page_number);
+ String text = mContext.getString(R.string.current_page_template,
+ pageInDocument + 1, mDocumentPageCount);
+ pageNumberView.setText(text);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mSelectedPageCount;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return computePageIndexInDocument(position);
+ }
+
+ @Override
+ public void onViewRecycled(ViewHolder holder) {
+ MyViewHolder myHolder = (MyViewHolder) holder;
+ PageContentView content = (PageContentView) holder.itemView
+ .findViewById(R.id.page_content);
+ recyclePageView(content, myHolder.mPageInAdapter);
+ myHolder.mPageInAdapter = INVALID_PAGE_INDEX;
+ }
+
+ public PageRange[] getRequestedPages() {
+ return mRequestedPages;
+ }
+
+ public PageRange[] getSelectedPages() {
+ PageRange[] selectedPages = computeSelectedPages();
+ if (!Arrays.equals(mSelectedPages, selectedPages)) {
+ mSelectedPages = selectedPages;
+ mSelectedPageCount = PageRangeUtils.getNormalizedPageCount(
+ mSelectedPages, mDocumentPageCount);
+ notifyDataSetChanged();
+ }
+ return mSelectedPages;
+ }
+
+ public void onPreviewAreaSizeChanged() {
+ if (mMediaSize != null) {
+ updatePreviewAreaAndPageSize();
+ notifyDataSetChanged();
+ }
+ }
+
+ private void updatePreviewAreaAndPageSize() {
+ final int availableWidth = mPreviewArea.getWidth();
+ final int availableHeight = mPreviewArea.getHeight();
+
+ // Page aspect ratio to keep.
+ final float pageAspectRatio = (float) mMediaSize.getWidthMils()
+ / mMediaSize.getHeightMils();
+
+ // Make sure we have no empty columns.
+ final int columnCount = Math.min(mSelectedPageCount, mColumnCount);
+ mPreviewArea.setColumnCount(columnCount);
+
+ // Compute max page width.
+ final int horizontalMargins = 2 * columnCount * mPreviewPageMargin;
+ final int horizontalPaddingAndMargins = horizontalMargins + 2 * mPreviewListPadding;
+ final int pageContentDesiredWidth = (int) ((((float) availableWidth
+ - horizontalPaddingAndMargins) / columnCount) + 0.5f);
+
+ // Compute max page height.
+ final int pageContentDesiredHeight = (int) (((float) pageContentDesiredWidth
+ / pageAspectRatio) + 0.5f);
+ final int pageContentMaxHeight = availableHeight - 2 * (mPreviewListPadding
+ + mPreviewPageMargin) - mFooterHeight;
+
+ mPageContentHeight = Math.min(pageContentDesiredHeight, pageContentMaxHeight);
+ mPageContentWidth = (int) ((mPageContentHeight * pageAspectRatio) + 0.5f);
+
+ final int totalContentWidth = columnCount * mPageContentWidth + horizontalMargins;
+ final int horizontalPadding = (availableWidth - totalContentWidth) / 2;
+
+ final int rowCount = mSelectedPageCount / columnCount
+ + ((mSelectedPageCount % columnCount) > 0 ? 1 : 0);
+ final int totalContentHeight = rowCount* (mPageContentHeight + mFooterHeight + 2
+ * mPreviewPageMargin);
+ final int verticalPadding = Math.max(mPreviewListPadding,
+ (availableHeight - totalContentHeight) / 2);
+
+ mPreviewArea.setPadding(horizontalPadding, verticalPadding,
+ horizontalPadding, verticalPadding);
+ }
+
+ private PageRange[] computeSelectedPages() {
+ ArrayList<PageRange> selectedPagesList = new ArrayList<>();
+
+ int startPageIndex = INVALID_PAGE_INDEX;
+ int endPageIndex = INVALID_PAGE_INDEX;
+
+ final int pageCount = mConfirmedPagesInDocument.size();
+ for (int i = 0; i < pageCount; i++) {
+ final int pageIndex = mConfirmedPagesInDocument.keyAt(i);
+ if (startPageIndex == INVALID_PAGE_INDEX) {
+ startPageIndex = endPageIndex = pageIndex;
+ }
+ if (endPageIndex + 1 < pageIndex) {
+ PageRange pageRange = new PageRange(startPageIndex, endPageIndex);
+ selectedPagesList.add(pageRange);
+ startPageIndex = pageIndex;
+ }
+ endPageIndex = pageIndex;
+ }
+
+ if (startPageIndex != INVALID_PAGE_INDEX
+ && endPageIndex != INVALID_PAGE_INDEX) {
+ PageRange pageRange = new PageRange(startPageIndex, endPageIndex);
+ selectedPagesList.add(pageRange);
+ }
+
+ PageRange[] selectedPages = new PageRange[selectedPagesList.size()];
+ selectedPagesList.toArray(selectedPages);
+
+ return selectedPages;
+ }
+
+ public void destroy() {
+ throwIfNotClosed();
+ doDestroy();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mState != STATE_DESTROYED) {
+ mCloseGuard.warnIfOpen();
+ doDestroy();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private int computePageIndexInDocument(int indexInAdapter) {
+ int skippedAdapterPages = 0;
+ final int selectedPagesCount = mSelectedPages.length;
+ for (int i = 0; i < selectedPagesCount; i++) {
+ PageRange pageRange = PageRangeUtils.asAbsoluteRange(
+ mSelectedPages[i], mDocumentPageCount);
+ skippedAdapterPages += pageRange.getSize();
+ if (skippedAdapterPages > indexInAdapter) {
+ final int overshoot = skippedAdapterPages - indexInAdapter - 1;
+ return pageRange.getEnd() - overshoot;
+ }
+ }
+ return INVALID_PAGE_INDEX;
+ }
+
+ private int computePageIndexInFile(int pageIndexInDocument) {
+ if (!PageRangeUtils.contains(mSelectedPages, pageIndexInDocument)) {
+ return INVALID_PAGE_INDEX;
+ }
+ if (mWrittenPages == null) {
+ return INVALID_PAGE_INDEX;
+ }
+
+ int indexInFile = INVALID_PAGE_INDEX;
+ final int rangeCount = mWrittenPages.length;
+ for (int i = 0; i < rangeCount; i++) {
+ PageRange pageRange = mWrittenPages[i];
+ if (!pageRange.contains(pageIndexInDocument)) {
+ indexInFile += pageRange.getSize();
+ } else {
+ indexInFile += pageIndexInDocument - pageRange.getStart() + 1;
+ return indexInFile;
+ }
+ }
+ return INVALID_PAGE_INDEX;
+ }
+
+ private void setConfirmedPages(PageRange[] pagesInDocument, int documentPageCount) {
+ mConfirmedPagesInDocument.clear();
+ final int rangeCount = pagesInDocument.length;
+ for (int i = 0; i < rangeCount; i++) {
+ PageRange pageRange = PageRangeUtils.asAbsoluteRange(pagesInDocument[i],
+ documentPageCount);
+ for (int j = pageRange.getStart(); j <= pageRange.getEnd(); j++) {
+ mConfirmedPagesInDocument.put(j, null);
+ }
+ }
+ }
+
+ private void onSelectedPageNotInFile(int pageInDocument) {
+ PageRange[] requestedPages = computeRequestedPages(pageInDocument);
+ if (!Arrays.equals(mRequestedPages, requestedPages)) {
+ mRequestedPages = requestedPages;
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Requesting pages: " + Arrays.toString(mRequestedPages));
+ }
+ mContentUpdateRequestCallback.requestContentUpdate();
+ }
+ }
+
+ private PageRange[] computeRequestedPages(int pageInDocument) {
+ if (mRequestedPages != null &&
+ PageRangeUtils.contains(mRequestedPages, pageInDocument)) {
+ return mRequestedPages;
+ }
+
+ List<PageRange> pageRangesList = new ArrayList<>();
+
+ int remainingPagesToRequest = MAX_PREVIEW_PAGES_BATCH;
+ final int selectedPagesCount = mSelectedPages.length;
+
+ // We always request the pages that are bound, i.e. shown on screen.
+ PageRange[] boundPagesInDocument = computeBoundPagesInDocument();
+
+ final int boundRangeCount = boundPagesInDocument.length;
+ for (int i = 0; i < boundRangeCount; i++) {
+ PageRange boundRange = boundPagesInDocument[i];
+ pageRangesList.add(boundRange);
+ }
+ remainingPagesToRequest -= PageRangeUtils.getNormalizedPageCount(
+ boundPagesInDocument, mDocumentPageCount);
+
+ final boolean requestFromStart = mRequestedPages == null
+ || pageInDocument > mRequestedPages[mRequestedPages.length - 1].getEnd();
+
+ if (!requestFromStart) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Requesting from end");
+ }
+
+ // Reminder that ranges are always normalized.
+ for (int i = selectedPagesCount - 1; i >= 0; i--) {
+ if (remainingPagesToRequest <= 0) {
+ break;
+ }
+
+ PageRange selectedRange = PageRangeUtils.asAbsoluteRange(mSelectedPages[i],
+ mDocumentPageCount);
+ if (pageInDocument < selectedRange.getStart()) {
+ continue;
+ }
+
+ PageRange pagesInRange;
+ int rangeSpan;
+
+ if (selectedRange.contains(pageInDocument)) {
+ rangeSpan = pageInDocument - selectedRange.getStart() + 1;
+ rangeSpan = Math.min(rangeSpan, remainingPagesToRequest);
+ final int fromPage = Math.max(pageInDocument - rangeSpan - 1, 0);
+ rangeSpan = Math.max(rangeSpan, 0);
+ pagesInRange = new PageRange(fromPage, pageInDocument);
+ } else {
+ rangeSpan = selectedRange.getSize();
+ rangeSpan = Math.min(rangeSpan, remainingPagesToRequest);
+ rangeSpan = Math.max(rangeSpan, 0);
+ final int fromPage = Math.max(selectedRange.getEnd() - rangeSpan - 1, 0);
+ final int toPage = selectedRange.getEnd();
+ pagesInRange = new PageRange(fromPage, toPage);
+ }
+
+ pageRangesList.add(pagesInRange);
+ remainingPagesToRequest -= rangeSpan;
+ }
+ } else {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Requesting from start");
+ }
+
+ // Reminder that ranges are always normalized.
+ for (int i = 0; i < selectedPagesCount; i++) {
+ if (remainingPagesToRequest <= 0) {
+ break;
+ }
+
+ PageRange selectedRange = PageRangeUtils.asAbsoluteRange(mSelectedPages[i],
+ mDocumentPageCount);
+ if (pageInDocument > selectedRange.getEnd()) {
+ continue;
+ }
+
+ PageRange pagesInRange;
+ int rangeSpan;
+
+ if (selectedRange.contains(pageInDocument)) {
+ rangeSpan = selectedRange.getEnd() - pageInDocument + 1;
+ rangeSpan = Math.min(rangeSpan, remainingPagesToRequest);
+ final int toPage = Math.min(pageInDocument + rangeSpan - 1,
+ mDocumentPageCount - 1);
+ pagesInRange = new PageRange(pageInDocument, toPage);
+ } else {
+ rangeSpan = selectedRange.getSize();
+ rangeSpan = Math.min(rangeSpan, remainingPagesToRequest);
+ final int fromPage = selectedRange.getStart();
+ final int toPage = Math.min(selectedRange.getStart() + rangeSpan - 1,
+ mDocumentPageCount - 1);
+ pagesInRange = new PageRange(fromPage, toPage);
+ }
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "computeRequestedPages() Adding range:" + pagesInRange);
+ }
+ pageRangesList.add(pagesInRange);
+ remainingPagesToRequest -= rangeSpan;
+ }
+ }
+
+ PageRange[] pageRanges = new PageRange[pageRangesList.size()];
+ pageRangesList.toArray(pageRanges);
+
+ return PageRangeUtils.normalize(pageRanges);
+ }
+
+ private PageRange[] computeBoundPagesInDocument() {
+ List<PageRange> pagesInDocumentList = new ArrayList<>();
+
+ int fromPage = INVALID_PAGE_INDEX;
+ int toPage = INVALID_PAGE_INDEX;
+
+ final int boundPageCount = mBoundPagesInAdapter.size();
+ for (int i = 0; i < boundPageCount; i++) {
+ // The container is a sparse array, so keys are sorted in ascending order.
+ final int boundPageInAdapter = mBoundPagesInAdapter.keyAt(i);
+ final int boundPageInDocument = computePageIndexInDocument(boundPageInAdapter);
+
+ if (fromPage == INVALID_PAGE_INDEX) {
+ fromPage = boundPageInDocument;
+ }
+
+ if (toPage == INVALID_PAGE_INDEX) {
+ toPage = boundPageInDocument;
+ }
+
+ if (boundPageInDocument > toPage + 1) {
+ PageRange pageRange = new PageRange(fromPage, toPage);
+ pagesInDocumentList.add(pageRange);
+ fromPage = toPage = boundPageInDocument;
+ } else {
+ toPage = boundPageInDocument;
+ }
+ }
+
+ if (fromPage != INVALID_PAGE_INDEX && toPage != INVALID_PAGE_INDEX) {
+ PageRange pageRange = new PageRange(fromPage, toPage);
+ pagesInDocumentList.add(pageRange);
+ }
+
+ PageRange[] pageInDocument = new PageRange[pagesInDocumentList.size()];
+ pagesInDocumentList.toArray(pageInDocument);
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Bound pages: " + Arrays.toString(pageInDocument));
+ }
+
+ return pageInDocument;
+ }
+
+ private void recyclePageView(PageContentView page, int pageIndexInAdapter) {
+ PageContentProvider provider = page.getPageContentProvider();
+ if (provider != null) {
+ page.init(null, null, null);
+ mPageContentRepository.releasePageContentProvider(provider);
+ mBoundPagesInAdapter.remove(pageIndexInAdapter);
+ }
+ page.setTag(null);
+ }
+
+ public void startPreloadContent(PageRange pageRangeInAdapter) {
+// final int startPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getStart());
+// final int startPageInFile = computePageIndexInFile(startPageInDocument);
+// final int endPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getEnd());
+// final int endPageInFile = computePageIndexInFile(endPageInDocument);
+// if (startPageInDocument != INVALID_PAGE_INDEX && endPageInDocument != INVALID_PAGE_INDEX) {
+// mPageContentRepository.startPreload(startPageInFile, endPageInFile);
+// }
+ }
+
+ public void stopPreloadContent() {
+// mPageContentRepository.stopPreload();
+ }
+
+ private void doDestroy() {
+ mPageContentRepository.destroy();
+ mCloseGuard.close();
+ mState = STATE_DESTROYED;
+ if (DEBUG) {
+ Log.i(LOG_TAG, "STATE_DESTROYED");
+ }
+ }
+
+ private void throwIfNotOpened() {
+ if (mState != STATE_OPENED) {
+ throw new IllegalStateException("Not opened");
+ }
+ }
+
+ private void throwIfNotClosed() {
+ if (mState != STATE_CLOSED) {
+ throw new IllegalStateException("Not closed");
+ }
+ }
+
+ private final class MyViewHolder extends ViewHolder {
+ int mPageInAdapter;
+
+ private MyViewHolder(View itemView) {
+ super(itemView);
+ }
+ }
+
+ private final class PageClickListener implements OnClickListener {
+ @Override
+ public void onClick(View page) {
+ MyViewHolder holder = (MyViewHolder) page.getTag();
+ final int pageInAdapter = holder.mPageInAdapter;
+ final int pageInDocument = computePageIndexInDocument(pageInAdapter);
+ CheckBox pageSelector = (CheckBox) page.findViewById(R.id.page_selector);
+ if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) < 0) {
+ mConfirmedPagesInDocument.put(pageInDocument, null);
+ pageSelector.setChecked(true);
+ page.animate().translationZ(mSelectedPageElevation);
+ } else {
+ mConfirmedPagesInDocument.remove(pageInDocument);
+ pageSelector.setChecked(false);
+ page.animate().translationZ(mUnselectedPageElevation);
+ }
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 3e0d7e5..7359221 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -26,6 +26,7 @@ import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -68,26 +69,39 @@ import android.widget.Spinner;
import android.widget.TextView;
import com.android.printspooler.R;
+import com.android.printspooler.model.MutexFileProvider;
import com.android.printspooler.model.PrintSpoolerProvider;
import com.android.printspooler.model.PrintSpoolerService;
import com.android.printspooler.model.RemotePrintDocument;
+import com.android.printspooler.model.RemotePrintDocument.RemotePrintDocumentInfo;
import com.android.printspooler.util.MediaSizeUtils;
import com.android.printspooler.util.MediaSizeUtils.MediaSizeComparator;
import com.android.printspooler.util.PageRangeUtils;
import com.android.printspooler.util.PrintOptionUtils;
-import com.android.printspooler.widget.ContentView;
-import com.android.printspooler.widget.ContentView.OptionsStateChangeListener;
-
-import java.util.*;
+import com.android.printspooler.widget.PrintContentView;
+import com.android.printspooler.widget.PrintContentView.OptionsStateChangeListener;
+import com.android.printspooler.widget.PrintContentView.OptionsStateController;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PrintActivity extends Activity implements RemotePrintDocument.UpdateResultCallbacks,
- PrintErrorFragment.OnActionListener, PrintProgressFragment.OnCancelRequestListener {
+ PrintErrorFragment.OnActionListener, PageAdapter.ContentUpdateRequestCallback,
+ OptionsStateChangeListener, OptionsStateController {
private static final String LOG_TAG = "PrintActivity";
+ private static final boolean DEBUG = true;
+
public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
+ private static final String FRAGMENT_TAG = "FRAGMENT_TAG";
+
private static final int ORIENTATION_PORTRAIT = 0;
private static final int ORIENTATION_LANDSCAPE = 1;
@@ -106,6 +120,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
private static final int STATE_UPDATE_FAILED = 3;
private static final int STATE_CREATE_FILE_FAILED = 4;
private static final int STATE_PRINTER_UNAVAILABLE = 5;
+ private static final int STATE_UPDATE_SLOW = 6;
private static final int UI_STATE_PREVIEW = 0;
private static final int UI_STATE_ERROR = 1;
@@ -120,10 +135,10 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
"(?=[]\\[+&|!(){}^\"~*?:\\\\])");
private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile(
- "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*?(([,])"
- + "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+");
+ "[\\s]*[0-9]+[\\-]?[\\s]*[0-9]*[\\s]*?(([,])"
+ + "[\\s]*[0-9]+[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+");
- public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {PageRange.ALL_PAGES};
+ public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[]{PageRange.ALL_PAGES};
private final PrinterAvailabilityDetector mPrinterAvailabilityDetector =
new PrinterAvailabilityDetector();
@@ -134,6 +149,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
private PrintSpoolerProvider mSpoolerProvider;
+ private PrintPreviewController mPrintPreviewController;
+
private PrintJobInfo mPrintJob;
private RemotePrintDocument mPrintedDocument;
private PrinterRegistry mPrinterRegistry;
@@ -158,7 +175,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
private Spinner mRangeOptionsSpinner;
- private ContentView mOptionsContent;
+ private PrintContentView mOptionsContent;
private TextView mSummaryCopies;
private TextView mSummaryPaperSize;
@@ -173,13 +190,13 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
private MediaSizeComparator mMediaSizeComparator;
- private PageRange[] mRequestedPages;
-
private PrinterInfo mOldCurrentPrinter;
+ private PageRange[] mSelectedPages;
+
private String mCallingPackageName;
- private int mState = STATE_CONFIGURING;
+ private int mState;
private int mUiState = UI_STATE_PREVIEW;
@@ -187,6 +204,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setState(STATE_CONFIGURING);
+
Bundle extras = getIntent().getExtras();
mPrintJob = extras.getParcelable(PrintManager.EXTRA_PRINT_JOB);
@@ -210,63 +229,97 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
new Runnable() {
@Override
public void run() {
- // Now that we are bound to the print spooler service,
- // create the printer registry and wait for it to get
- // the first batch of results which will be delivered
- // after reading historical data. This should be pretty
- // fast, so just wait before showing the UI.
- mPrinterRegistry = new PrinterRegistry(PrintActivity.this,
- new Runnable() {
- @Override
- public void run() {
- setTitle(R.string.print_dialog);
- setContentView(R.layout.print_activity);
+ onConnectedToPrintSpooler(adapter);
+ }
+ });
+ }
+
+ private void onConnectedToPrintSpooler(final IBinder documentAdapter) {
+ // Now that we are bound to the print spooler service,
+ // create the printer registry and wait for it to get
+ // the first batch of results which will be delivered
+ // after reading historical data. This should be pretty
+ // fast, so just wait before showing the UI.
+ mPrinterRegistry = new PrinterRegistry(PrintActivity.this,
+ new Runnable() {
+ @Override
+ public void run() {
+ onPrinterRegistryReady(documentAdapter);
+ }
+ });
+ }
- mPrintedDocument = new RemotePrintDocument(PrintActivity.this,
- IPrintDocumentAdapter.Stub.asInterface(adapter),
- PrintSpoolerService.generateFileForPrintJob(PrintActivity.this,
- mPrintJob.getId()),
- new RemotePrintDocument.DocumentObserver() {
- @Override
- public void onDestroy() {
- finish();
- }
- }, PrintActivity.this);
+ private void onPrinterRegistryReady(IBinder documentAdapter) {
+ // Now that we are bound to the local print spooler service
+ // and the printer registry loaded the historical printers
+ // we can show the UI without flickering.
+ setTitle(R.string.print_dialog);
+ setContentView(R.layout.print_activity);
+
+ final MutexFileProvider fileProvider;
+ try {
+ fileProvider = new MutexFileProvider(
+ PrintSpoolerService.generateFileForPrintJob(
+ PrintActivity.this, mPrintJob.getId()));
+ } catch (IOException ioe) {
+ // At this point we cannot recover, so just take it down.
+ throw new IllegalStateException("Cannot create print job file", ioe);
+ }
+
+ mPrintPreviewController = new PrintPreviewController(PrintActivity.this,
+ fileProvider);
+
+ mPrintedDocument = new RemotePrintDocument(PrintActivity.this,
+ IPrintDocumentAdapter.Stub.asInterface(documentAdapter),
+ fileProvider, new RemotePrintDocument.DocumentObserver() {
+ @Override
+ public void onDestroy() {
+ finish();
+ }
+ }, PrintActivity.this);
- mProgressMessageController = new ProgressMessageController(PrintActivity.this);
+ mProgressMessageController = new ProgressMessageController(
+ PrintActivity.this);
- mMediaSizeComparator = new MediaSizeComparator(PrintActivity.this);
+ mMediaSizeComparator = new MediaSizeComparator(PrintActivity.this);
- mDestinationSpinnerAdapter = new DestinationAdapter();
+ mDestinationSpinnerAdapter = new DestinationAdapter();
- bindUi();
+ bindUi();
- updateOptionsUi();
+ updateOptionsUi();
- // Now show the updated UI to avoid flicker.
- mOptionsContent.setVisibility(View.VISIBLE);
+ // Now show the updated UI to avoid flicker.
+ mOptionsContent.setVisibility(View.VISIBLE);
- mRequestedPages = computeRequestedPages();
+ mSelectedPages = computeSelectedPages();
- mPrintedDocument.start();
+ mPrintedDocument.start();
- ensurePreviewUiShown();
- }
- });
- }
- });
+ ensurePreviewUiShown();
}
@Override
public void onPause() {
if (isFinishing()) {
PrintSpoolerService spooler = mSpoolerProvider.getSpooler();
- if (mState == STATE_PRINT_CONFIRMED) {
- spooler.updatePrintJobUserConfigurableOptionsNoPersistence(mPrintJob);
- spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_QUEUED, null);
- } else {
- spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null);
+ spooler.updatePrintJobUserConfigurableOptionsNoPersistence(mPrintJob);
+
+ switch (mState) {
+ case STATE_PRINT_CONFIRMED: {
+ spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_QUEUED, null);
+ } break;
+
+ case STATE_CREATE_FILE_FAILED: {
+ spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_FAILED,
+ getString(R.string.print_write_error_message));
+ } break;
+
+ default: {
+ spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null);
+ } break;
}
+
mProgressMessageController.cancel();
mPrinterRegistry.setTrackedPrinter(null);
mSpoolerProvider.destroy();
@@ -292,48 +345,59 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK
&& event.isTracking() && !event.isCanceled()) {
- cancelPrint();
+ if (mPrintPreviewController != null&&mPrintPreviewController.isOptionsOpened() && !hasErrors()) {
+ mPrintPreviewController.closeOptions();
+ } else {
+ cancelPrint();
+ }
return true;
}
return super.onKeyUp(keyCode, event);
}
@Override
- public void onActionPerformed() {
- switch (mState) {
- case STATE_UPDATE_FAILED: {
- if (canUpdateDocument()) {
- updateDocument(true, true);
- ensurePreviewUiShown();
- mState = STATE_CONFIGURING;
- updateOptionsUi();
- }
- } break;
-
- case STATE_CREATE_FILE_FAILED: {
- mState = STATE_CONFIGURING;
- ensurePreviewUiShown();
- updateOptionsUi();
- } break;
+ public void requestContentUpdate() {
+ if (canUpdateDocument()) {
+ updateDocument(true, false);
}
}
@Override
- public void onCancelRequest() {
- if (mPrintedDocument.isUpdating()) {
- mPrintedDocument.cancel();
+ public void onActionPerformed() {
+ if (mState == STATE_UPDATE_FAILED
+ && canUpdateDocument()) {
+ updateDocument(true, true);
+ ensurePreviewUiShown();
+ setState(STATE_CONFIGURING);
+ updateOptionsUi();
}
}
public void onUpdateCanceled() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onUpdateCanceled()");
+ }
+
mProgressMessageController.cancel();
ensurePreviewUiShown();
- finishIfConfirmedOrCanceled();
- updateOptionsUi();
+
+ switch (mState) {
+ case STATE_PRINT_CONFIRMED: {
+ requestCreatePdfFileOrFinish();
+ } break;
+
+ case STATE_PRINT_CANCELED: {
+ finish();
+ } break;
+ }
}
@Override
- public void onUpdateCompleted(RemotePrintDocument.RemotePrintDocumentInfo document) {
+ public void onUpdateCompleted(RemotePrintDocumentInfo document) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onUpdateCompleted()");
+ }
+
mProgressMessageController.cancel();
ensurePreviewUiShown();
@@ -343,42 +407,120 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
// print service perspective which is the pages in the written PDF not the
// pages in the printed document.
PrintDocumentInfo info = document.info;
- if (info == null) {
- return;
+ if (info != null) {
+ final int pageCount = PageRangeUtils.getNormalizedPageCount(document.writtenPages,
+ getAdjustedPageCount(info));
+ PrintDocumentInfo adjustedInfo = new PrintDocumentInfo.Builder(info.getName())
+ .setContentType(info.getContentType())
+ .setPageCount(pageCount)
+ .build();
+ mPrintJob.setDocumentInfo(adjustedInfo);
+ mPrintJob.setPages(document.printedPages);
+ }
+
+ switch (mState) {
+ case STATE_PRINT_CONFIRMED: {
+ requestCreatePdfFileOrFinish();
+ } break;
+
+ case STATE_PRINT_CANCELED: {
+ finish();
+ } break;
+
+ default: {
+ updatePrintPreviewController(document.changed);
+
+ setState(STATE_CONFIGURING);
+ updateOptionsUi();
+ } break;
}
- final int pageCount = PageRangeUtils.getNormalizedPageCount(document.writtenPages,
- info.getPageCount());
- PrintDocumentInfo adjustedInfo = new PrintDocumentInfo.Builder(info.getName())
- .setContentType(info.getContentType())
- .setPageCount(pageCount)
- .build();
- mPrintJob.setDocumentInfo(adjustedInfo);
- mPrintJob.setPages(document.printedPages);
- finishIfConfirmedOrCanceled();
- updateOptionsUi();
}
@Override
public void onUpdateFailed(CharSequence error) {
- mState = STATE_UPDATE_FAILED;
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onUpdateFailed()");
+ }
+
+ mProgressMessageController.cancel();
ensureErrorUiShown(error, PrintErrorFragment.ACTION_RETRY);
+
+ setState(STATE_UPDATE_FAILED);
+
updateOptionsUi();
}
@Override
+ public void onOptionsOpened() {
+ updateSelectedPagesFromPreview();
+ }
+
+ @Override
+ public void onOptionsClosed() {
+ PageRange[] selectedPages = computeSelectedPages();
+ if (!Arrays.equals(mSelectedPages, selectedPages)) {
+ mSelectedPages = selectedPages;
+
+ // Update preview.
+ updatePrintPreviewController(false);
+ }
+
+ // Make sure the IME is not on the way of preview as
+ // the user may have used it to type copies or range.
+ InputMethodManager imm = (InputMethodManager) getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(mDestinationSpinner.getWindowToken(), 0);
+ }
+
+ private void updatePrintPreviewController(boolean contentUpdated) {
+ // If we have not heard from the application, do nothing.
+ RemotePrintDocumentInfo documentInfo = mPrintedDocument.getDocumentInfo();
+ if (!documentInfo.laidout) {
+ return;
+ }
+
+ // Update the preview controller.
+ mPrintPreviewController.onContentUpdated(contentUpdated,
+ getAdjustedPageCount(documentInfo.info),
+ mPrintedDocument.getDocumentInfo().writtenPages,
+ mSelectedPages, mPrintJob.getAttributes().getMediaSize(),
+ mPrintJob.getAttributes().getMinMargins());
+ }
+
+
+ @Override
+ public boolean canOpenOptions() {
+ return true;
+ }
+
+ @Override
+ public boolean canCloseOptions() {
+ return !hasErrors();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mPrintPreviewController.onOrientationChanged();
+ }
+
+ @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case ACTIVITY_REQUEST_CREATE_FILE: {
onStartCreateDocumentActivityResult(resultCode, data);
- } break;
+ }
+ break;
case ACTIVITY_REQUEST_SELECT_PRINTER: {
onSelectPrinterActivityResult(resultCode, data);
- } break;
+ }
+ break;
case ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS: {
onAdvancedPrintOptionsActivityResult(resultCode, data);
- } break;
+ }
+ break;
}
}
@@ -398,15 +540,28 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
if (resultCode == RESULT_OK && data != null) {
Uri uri = data.getData();
mPrintedDocument.writeContent(getContentResolver(), uri);
- finish();
+ // Calling finish here does not invoke lifecycle callbacks but we
+ // update the print job in onPause if finishing, hence post a message.
+ mDestinationSpinner.post(new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ });
} else if (resultCode == RESULT_CANCELED) {
- mState = STATE_CONFIGURING;
+ setState(STATE_CONFIGURING);
updateOptionsUi();
} else {
- ensureErrorUiShown(getString(R.string.print_write_error_message),
- PrintErrorFragment.ACTION_CONFIRM);
- mState = STATE_CREATE_FILE_FAILED;
+ setState(STATE_CREATE_FILE_FAILED);
updateOptionsUi();
+ // Calling finish here does not invoke lifecycle callbacks but we
+ // update the print job in onPause if finishing, hence post a message.
+ mDestinationSpinner.post(new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ });
}
}
@@ -526,74 +681,105 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
}
- // Take the page range only if it is valid.
+ PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
+ final int pageCount = (info != null) ? getAdjustedPageCount(info) : 0;
PageRange[] pageRanges = printJobInfo.getPages();
- if (pageRanges != null && pageRanges.length > 0) {
- pageRanges = PageRangeUtils.normalize(pageRanges);
+ updateSelectedPages(pageRanges, pageCount);
- PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
- final int pageCount = (info != null) ? info.getPageCount() : 0;
+ // Update the content if needed.
+ if (canUpdateDocument()) {
+ updateDocument(true, false);
+ }
+ }
- // Handle the case where all pages are specified explicitly
- // instead of the *all pages* constant.
- if (pageRanges.length == 1) {
- if (pageRanges[0].getStart() == 0 && pageRanges[0].getEnd() == pageCount - 1) {
- pageRanges[0] = PageRange.ALL_PAGES;
- }
+ private void setState(int state) {
+ if (isFinalState(mState)) {
+ if (isFinalState(state)) {
+ mState = state;
}
+ } else {
+ mState = state;
+ }
+ }
- if (Arrays.equals(pageRanges, ALL_PAGES_ARRAY)) {
- mPrintJob.setPages(pageRanges);
+ private static boolean isFinalState(int state) {
+ return state == STATE_PRINT_CONFIRMED
+ || state == STATE_PRINT_CANCELED;
+ }
- if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
- mRangeOptionsSpinner.setSelection(0);
- }
- } else if (pageRanges[0].getStart() >= 0
- && pageRanges[pageRanges.length - 1].getEnd() < pageCount) {
- mPrintJob.setPages(pageRanges);
+ private void updateSelectedPagesFromPreview() {
+ PageRange[] selectedPages = mPrintPreviewController.getSelectedPages();
+ if (!Arrays.equals(mSelectedPages, selectedPages)) {
+ updateSelectedPages(selectedPages,
+ getAdjustedPageCount(mPrintedDocument.getDocumentInfo().info));
+ }
+ }
- if (mRangeOptionsSpinner.getSelectedItemPosition() != 1) {
- mRangeOptionsSpinner.setSelection(1);
- }
+ private void updateSelectedPages(PageRange[] selectedPages, int pageInDocumentCount) {
+ if (selectedPages == null || selectedPages.length <= 0) {
+ return;
+ }
- StringBuilder builder = new StringBuilder();
- final int pageRangeCount = pageRanges.length;
- for (int i = 0; i < pageRangeCount; i++) {
- if (builder.length() > 0) {
- builder.append(',');
- }
+ selectedPages = PageRangeUtils.normalize(selectedPages);
- final int shownStartPage;
- final int shownEndPage;
- PageRange pageRange = pageRanges[i];
- if (pageRange.equals(PageRange.ALL_PAGES)) {
- shownStartPage = 1;
- shownEndPage = pageCount;
- } else {
- shownStartPage = pageRange.getStart() + 1;
- shownEndPage = pageRange.getEnd() + 1;
- }
+ // Handle the case where all pages are specified explicitly
+ // instead of the *all pages* constant.
+ if (PageRangeUtils.isAllPages(selectedPages, pageInDocumentCount)) {
+ selectedPages = new PageRange[] {PageRange.ALL_PAGES};
+ }
- builder.append(shownStartPage);
+ if (Arrays.equals(mSelectedPages, selectedPages)) {
+ return;
+ }
- if (shownStartPage != shownEndPage) {
- builder.append('-');
- builder.append(shownEndPage);
- }
+ mSelectedPages = selectedPages;
+ mPrintJob.setPages(selectedPages);
+
+ if (Arrays.equals(selectedPages, ALL_PAGES_ARRAY)) {
+ if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
+ mRangeOptionsSpinner.setSelection(0);
+ mPageRangeEditText.setText("");
+ }
+ } else if (selectedPages[0].getStart() >= 0
+ && selectedPages[selectedPages.length - 1].getEnd() < pageInDocumentCount) {
+ if (mRangeOptionsSpinner.getSelectedItemPosition() != 1) {
+ mRangeOptionsSpinner.setSelection(1);
+ }
+
+ StringBuilder builder = new StringBuilder();
+ final int pageRangeCount = selectedPages.length;
+ for (int i = 0; i < pageRangeCount; i++) {
+ if (builder.length() > 0) {
+ builder.append(',');
+ }
+
+ final int shownStartPage;
+ final int shownEndPage;
+ PageRange pageRange = selectedPages[i];
+ if (pageRange.equals(PageRange.ALL_PAGES)) {
+ shownStartPage = 1;
+ shownEndPage = pageInDocumentCount;
+ } else {
+ shownStartPage = pageRange.getStart() + 1;
+ shownEndPage = pageRange.getEnd() + 1;
+ }
+
+ builder.append(shownStartPage);
+
+ if (shownStartPage != shownEndPage) {
+ builder.append('-');
+ builder.append(shownEndPage);
}
- mPageRangeEditText.setText(builder.toString());
}
- }
- // Update the content if needed.
- if (canUpdateDocument()) {
- updateDocument(true, false);
+ mPageRangeEditText.setText(builder.toString());
}
}
private void ensureProgressUiShown() {
if (mUiState != UI_STATE_PROGRESS) {
mUiState = UI_STATE_PROGRESS;
+ mPrintPreviewController.setUiShown(false);
Fragment fragment = PrintProgressFragment.newInstance();
showFragment(fragment);
}
@@ -602,28 +788,31 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
private void ensurePreviewUiShown() {
if (mUiState != UI_STATE_PREVIEW) {
mUiState = UI_STATE_PREVIEW;
- Fragment fragment = PrintPreviewFragment.newInstance();
- showFragment(fragment);
+ mPrintPreviewController.setUiShown(true);
+ showFragment(null);
}
}
private void ensureErrorUiShown(CharSequence message, int action) {
if (mUiState != UI_STATE_ERROR) {
mUiState = UI_STATE_ERROR;
+ mPrintPreviewController.setUiShown(false);
Fragment fragment = PrintErrorFragment.newInstance(message, action);
showFragment(fragment);
}
}
- private void showFragment(Fragment fragment) {
+ private void showFragment(Fragment newFragment) {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
- Fragment oldFragment = getFragmentManager().findFragmentById(
- R.id.embedded_content_container);
+ Fragment oldFragment = getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
if (oldFragment != null) {
transaction.remove(oldFragment);
}
- transaction.add(R.id.embedded_content_container, fragment);
+ if (newFragment != null) {
+ transaction.add(R.id.embedded_content_container, newFragment, FRAGMENT_TAG);
+ }
transaction.commit();
+ getFragmentManager().executePendingTransactions();
}
private void requestCreatePdfFileOrFinish() {
@@ -634,14 +823,6 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
}
- private void finishIfConfirmedOrCanceled() {
- if (mState == STATE_PRINT_CONFIRMED) {
- requestCreatePdfFileOrFinish();
- } else if (mState == STATE_PRINT_CANCELED) {
- finish();
- }
- }
-
private void updatePrintAttributesFromCapabilities(PrinterCapabilitiesInfo capabilities) {
PrintAttributes defaults = capabilities.getDefaults();
@@ -700,21 +881,23 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
mPrintedDocument.clearUpdateError();
}
- if (mRequestedPages != null && mRequestedPages.length > 0) {
- final PageRange[] pages;
- if (preview) {
- final int firstPage = mRequestedPages[0].getStart();
- pages = new PageRange[]{new PageRange(firstPage, firstPage)};
- } else {
- pages = mRequestedPages;
- }
- final boolean willUpdate = mPrintedDocument.update(mPrintJob.getAttributes(),
- pages, preview);
+ final PageRange[] pages;
+ if (preview) {
+ pages = mPrintPreviewController.getRequestedPages();
+ } else {
+ pages = mPrintPreviewController.getSelectedPages();
+ }
- if (willUpdate) {
- mProgressMessageController.post();
- return true;
- }
+ final boolean willUpdate = mPrintedDocument.update(mPrintJob.getAttributes(),
+ pages, preview);
+
+ if (willUpdate) {
+ // When the update is done we update the print preview.
+ mProgressMessageController.post();
+ return true;
+ } else {
+ // Update preview.
+ updatePrintPreviewController(false);
}
return false;
@@ -735,7 +918,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
private void cancelPrint() {
- mState = STATE_PRINT_CANCELED;
+ setState(STATE_PRINT_CANCELED);
updateOptionsUi();
if (mPrintedDocument.isUpdating()) {
mPrintedDocument.cancel();
@@ -744,12 +927,25 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
private void confirmPrint() {
- mState = STATE_PRINT_CONFIRMED;
+ setState(STATE_PRINT_CONFIRMED);
+
updateOptionsUi();
+ addCurrentPrinterToHistory();
+
+ PageRange[] selectedPages = computeSelectedPages();
+ if (!Arrays.equals(mSelectedPages, selectedPages)) {
+ mSelectedPages = selectedPages;
+ // Update preview.
+ updatePrintPreviewController(false);
+ }
+
+ updateSelectedPagesFromPreview();
+ mPrintPreviewController.closeOptions();
+
if (canUpdateDocument()) {
updateDocument(false, false);
}
- addCurrentPrinterToHistory();
+
if (!mPrintedDocument.isUpdating()) {
requestCreatePdfFileOrFinish();
}
@@ -761,18 +957,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
mSummaryPaperSize = (TextView) findViewById(R.id.paper_size_summary);
// Options container
- mOptionsContent = (ContentView) findViewById(R.id.options_content);
- mOptionsContent.setOptionsStateChangeListener(new OptionsStateChangeListener() {
- @Override
- public void onOptionsOpened() {
- // TODO: Update preview.
- }
-
- @Override
- public void onOptionsClosed() {
- // TODO: Update preview.
- }
- });
+ mOptionsContent = (PrintContentView) findViewById(R.id.options_content);
+ mOptionsContent.setOptionsStateChangeListener(this);
+ mOptionsContent.setOpenOptionsController(this);
OnItemSelectedListener itemSelectedListener = new MyOnItemSelectedListener();
OnClickListener clickListener = new MyClickListener();
@@ -809,7 +996,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
mOrientationSpinnerAdapter = new ArrayAdapter<>(
this, R.layout.spinner_dropdown_item, R.id.title);
String[] orientationLabels = getResources().getStringArray(
- R.array.orientation_labels);
+ R.array.orientation_labels);
mOrientationSpinnerAdapter.add(new SpinnerItem<>(
ORIENTATION_PORTRAIT, orientationLabels[0]));
mOrientationSpinnerAdapter.add(new SpinnerItem<>(
@@ -844,15 +1031,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
// Advanced options button.
mAdvancedPrintOptionsContainer = findViewById(R.id.more_options_container);
mMoreOptionsButton = (Button) findViewById(R.id.more_options_button);
- mMoreOptionsButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- PrinterInfo currentPrinter = getCurrentPrinter();
- if (currentPrinter != null) {
- startAdvancedPrintOptionsActivity(currentPrinter);
- }
- }
- });
+ mMoreOptionsButton.setOnClickListener(clickListener);
// Print button
mPrintButton = (ImageView) findViewById(R.id.print_button);
@@ -883,7 +1062,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
&& printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
}
- private void updateOptionsUi() {
+ void updateOptionsUi() {
// Always update the summary.
if (!TextUtils.isEmpty(mCopiesEditText.getText())) {
mSummaryCopies.setText(mCopiesEditText.getText());
@@ -899,7 +1078,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
|| mState == STATE_PRINT_CANCELED
|| mState == STATE_UPDATE_FAILED
|| mState == STATE_CREATE_FILE_FAILED
- || mState == STATE_PRINTER_UNAVAILABLE) {
+ || mState == STATE_PRINTER_UNAVAILABLE
+ || mState == STATE_UPDATE_SLOW) {
if (mState != STATE_PRINTER_UNAVAILABLE) {
mDestinationSpinner.setEnabled(false);
}
@@ -909,14 +1089,14 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
mOrientationSpinner.setEnabled(false);
mRangeOptionsSpinner.setEnabled(false);
mPageRangeEditText.setEnabled(false);
- mPrintButton.setEnabled(false);
+ mPrintButton.setVisibility(View.GONE);
mMoreOptionsButton.setEnabled(false);
return;
}
// If no current printer, or it has no capabilities, or it is not
// available, we disable all print options except the destination.
- PrinterInfo currentPrinter = getCurrentPrinter();
+ PrinterInfo currentPrinter = getCurrentPrinter();
if (currentPrinter == null || !canPrint(currentPrinter)) {
mCopiesEditText.setEnabled(false);
mMediaSizeSpinner.setEnabled(false);
@@ -924,7 +1104,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
mOrientationSpinner.setEnabled(false);
mRangeOptionsSpinner.setEnabled(false);
mPageRangeEditText.setEnabled(false);
- mPrintButton.setEnabled(false);
+ mPrintButton.setVisibility(View.GONE);
mMoreOptionsButton.setEnabled(false);
return;
}
@@ -932,6 +1112,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
PrintAttributes defaultAttributes = capabilities.getDefaults();
+ // Destination.
+ mDestinationSpinner.setEnabled(true);
+
// Media size.
mMediaSizeSpinner.setEnabled(true);
@@ -1075,8 +1258,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
// Range options
PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
- if (info != null && info.getPageCount() > 0) {
- if (info.getPageCount() == 1) {
+ final int pageCount = getAdjustedPageCount(info);
+ if (info != null && pageCount > 0) {
+ if (pageCount == 1) {
mRangeOptionsSpinner.setEnabled(false);
} else {
mRangeOptionsSpinner.setEnabled(true);
@@ -1096,13 +1280,14 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
mPageRangeTitle.setVisibility(View.INVISIBLE);
}
}
- String title = (info.getPageCount() != PrintDocumentInfo.PAGE_COUNT_UNKNOWN)
- ? getString(R.string.label_pages, String.valueOf(info.getPageCount()))
+ String title = (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN)
+ ? getString(R.string.label_pages, String.valueOf(pageCount))
: getString(R.string.page_count_unknown);
mPageRangeOptionsTitle.setText(title);
} else {
if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
mRangeOptionsSpinner.setSelection(0);
+ mPageRangeEditText.setText("");
}
mRangeOptionsSpinner.setEnabled(false);
mPageRangeOptionsTitle.setText(getString(R.string.page_count_unknown));
@@ -1130,11 +1315,11 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1
&& (TextUtils.isEmpty(mPageRangeEditText.getText()) || hasErrors()))
- || (mRangeOptionsSpinner.getSelectedItemPosition() == 0
+ || (mRangeOptionsSpinner.getSelectedItemPosition() == 0
&& (mPrintedDocument.getDocumentInfo() == null || hasErrors()))) {
- mPrintButton.setEnabled(false);
+ mPrintButton.setVisibility(View.GONE);
} else {
- mPrintButton.setEnabled(true);
+ mPrintButton.setVisibility(View.VISIBLE);
}
// Copies
@@ -1150,7 +1335,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
}
- private PageRange[] computeRequestedPages() {
+ private PageRange[] computeSelectedPages() {
if (hasErrors()) {
return null;
}
@@ -1197,16 +1382,28 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
return ALL_PAGES_ARRAY;
}
+ private int getAdjustedPageCount(PrintDocumentInfo info) {
+ if (info != null) {
+ final int pageCount = info.getPageCount();
+ if (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
+ return pageCount;
+ }
+ }
+ // If the app does not tell us how many pages are in the
+ // doc we ask for all pages and use the document page count.
+ return mPrintPreviewController.getFilePageCount();
+ }
+
private boolean hasErrors() {
return (mCopiesEditText.getError() != null)
|| (mPageRangeEditText.getVisibility() == View.VISIBLE
- && mPageRangeEditText.getError() != null);
+ && mPageRangeEditText.getError() != null);
}
public void onPrinterAvailable(PrinterInfo printer) {
PrinterInfo currentPrinter = getCurrentPrinter();
if (currentPrinter.equals(printer)) {
- mState = STATE_CONFIGURING;
+ setState(STATE_CONFIGURING);
if (canUpdateDocument()) {
updateDocument(true, false);
}
@@ -1217,7 +1414,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
public void onPrinterUnavailable(PrinterInfo printer) {
if (getCurrentPrinter().getId().equals(printer.getId())) {
- mState = STATE_PRINTER_UNAVAILABLE;
+ setState(STATE_PRINTER_UNAVAILABLE);
if (mPrintedDocument.isUpdating()) {
mPrintedDocument.cancel();
}
@@ -1227,6 +1424,47 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
}
+ private boolean canUpdateDocument() {
+ if (mPrintedDocument.isDestroyed()) {
+ return false;
+ }
+
+ if (hasErrors()) {
+ return false;
+ }
+
+ PrintAttributes attributes = mPrintJob.getAttributes();
+
+ final int colorMode = attributes.getColorMode();
+ if (colorMode != PrintAttributes.COLOR_MODE_COLOR
+ && colorMode != PrintAttributes.COLOR_MODE_MONOCHROME) {
+ return false;
+ }
+ if (attributes.getMediaSize() == null) {
+ return false;
+ }
+ if (attributes.getMinMargins() == null) {
+ return false;
+ }
+ if (attributes.getResolution() == null) {
+ return false;
+ }
+
+ PrinterInfo currentPrinter = getCurrentPrinter();
+ if (currentPrinter == null) {
+ return false;
+ }
+ PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
+ if (capabilities == null) {
+ return false;
+ }
+ if (currentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
+ return false;
+ }
+
+ return true;
+ }
+
private final class SpinnerItem<T> {
final T value;
final CharSequence label;
@@ -1266,10 +1504,10 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
mPrinter = new PrinterInfo.Builder(printer).build();
} else {
notifyIfAvailable =
- (mPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE
- && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE)
- || (mPrinter.getCapabilities() == null
- && printer.getCapabilities() != null);
+ (mPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE
+ && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE)
+ || (mPrinter.getCapabilities() == null
+ && printer.getCapabilities() != null);
mPrinter.copyFrom(printer);
}
@@ -1651,15 +1889,14 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
oldPrinterState.copyFrom(newPrinterState);
if ((isActive && gotCapab) || (becameActive && hasCapab)) {
+ if (hasCapab && capabChanged) {
+ updatePrintAttributesFromCapabilities(newCapab);
+ }
onPrinterAvailable(newPrinterState);
- } else if ((becameInactive && hasCapab)|| (isActive && lostCapab)) {
+ } else if ((becameInactive && hasCapab) || (isActive && lostCapab)) {
onPrinterUnavailable(newPrinterState);
}
- if (hasCapab && capabChanged) {
- updatePrintAttributesFromCapabilities(newCapab);
- }
-
final boolean updateNeeded = ((capabChanged && hasCapab && isActive)
|| (becameActive && hasCapab) || (isActive && gotCapab));
@@ -1702,11 +1939,13 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
if (mOldCurrentPrinter == currentPrinter) {
return;
}
+
mOldCurrentPrinter = currentPrinter;
PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
currentPrinter.getId());
if (!printerHolder.removed) {
+ setState(STATE_CONFIGURING);
mDestinationSpinnerAdapter.pruneRemovedPrinters();
ensurePreviewUiShown();
}
@@ -1718,16 +1957,17 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
if (capabilities != null) {
- updatePrintAttributesFromCapabilities(capabilities);
+ updatePrintAttributesFromCapabilities(capabilities);
}
mPrinterAvailabilityDetector.updatePrinter(currentPrinter);
} else if (spinner == mMediaSizeSpinner) {
SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
+ PrintAttributes attributes = mPrintJob.getAttributes();
if (mOrientationSpinner.getSelectedItemPosition() == 0) {
- mPrintJob.getAttributes().setMediaSize(mediaItem.value.asPortrait());
+ attributes.setMediaSize(mediaItem.value.asPortrait());
} else {
- mPrintJob.getAttributes().setMediaSize(mediaItem.value.asLandscape());
+ attributes.setMediaSize(mediaItem.value.asLandscape());
}
} else if (spinner == mColorModeSpinner) {
SpinnerItem<Integer> colorModeItem = mColorModeSpinnerAdapter.getItem(position);
@@ -1742,6 +1982,12 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
attributes.copyFrom(attributes.asLandscape());
}
}
+ } else if (spinner == mRangeOptionsSpinner) {
+ if (mRangeOptionsSpinner.getSelectedItemPosition() == 0) {
+ mPageRangeEditText.setText("");
+ } else if (TextUtils.isEmpty(mPageRangeEditText.getText())) {
+ mPageRangeEditText.setError("");
+ }
}
if (canUpdateDocument()) {
@@ -1757,47 +2003,6 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
}
- private boolean canUpdateDocument() {
- if (mPrintedDocument.isDestroyed()) {
- return false;
- }
-
- if (hasErrors()) {
- return false;
- }
-
- PrintAttributes attributes = mPrintJob.getAttributes();
-
- final int colorMode = attributes.getColorMode();
- if (colorMode != PrintAttributes.COLOR_MODE_COLOR
- && colorMode != PrintAttributes.COLOR_MODE_MONOCHROME) {
- return false;
- }
- if (attributes.getMediaSize() == null) {
- return false;
- }
- if (attributes.getMinMargins() == null) {
- return false;
- }
- if (attributes.getResolution() == null) {
- return false;
- }
-
- PrinterInfo currentPrinter = getCurrentPrinter();
- if (currentPrinter == null) {
- return false;
- }
- PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
- if (capabilities == null) {
- return false;
- }
- if (currentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
- return false;
- }
-
- return true;
- }
-
private final class SelectAllOnFocusListener implements OnFocusChangeListener {
@Override
public void onFocusChange(View view, boolean hasFocus) {
@@ -1839,7 +2044,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
- final int pageCount = (info != null) ? info.getPageCount() : 0;
+ final int pageCount = (info != null) ? getAdjustedPageCount(info) : 0;
// The range
Matcher matcher = PATTERN_DIGITS.matcher(text);
@@ -1860,10 +2065,6 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
// greater than the to page. When computing the requested pages
// we just swap them if necessary.
- // Keep the print job up to date with the selected pages if we
- // know how many pages are there in the document.
- mRequestedPages = computeRequestedPages();
-
mPageRangeEditText.setError(null);
mPrintButton.setEnabled(true);
updateOptionsUi();
@@ -1949,7 +2150,10 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
@Override
public void run() {
+ mPosted = false;
+ setState(STATE_UPDATE_SLOW);
ensureProgressUiShown();
+ updateOptionsUi();
}
}
-}
+} \ No newline at end of file
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintErrorFragment.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintErrorFragment.java
index b708356..b0b9c96 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintErrorFragment.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintErrorFragment.java
@@ -35,22 +35,21 @@ import com.android.printspooler.R;
public final class PrintErrorFragment extends Fragment {
public static final int ACTION_NONE = 0;
public static final int ACTION_RETRY = 1;
- public static final int ACTION_CONFIRM = 2;
+
+ private static final String EXTRA_MESSAGE = "EXTRA_MESSAGE";
+ private static final String EXTRA_ACTION = "EXTRA_ACTION";
public interface OnActionListener {
public void onActionPerformed();
}
- private static final String EXTRA_ERROR_MESSAGE = "EXTRA_ERROR_MESSAGE";
- private static final String EXTRA_ACTION = "EXTRA_ACTION";
-
- public static PrintErrorFragment newInstance(CharSequence errorMessage, int action) {
- PrintErrorFragment instance = new PrintErrorFragment();
+ public static PrintErrorFragment newInstance(CharSequence message, int action) {
Bundle arguments = new Bundle();
- arguments.putCharSequence(EXTRA_ERROR_MESSAGE, errorMessage);
+ arguments.putCharSequence(EXTRA_MESSAGE, message);
arguments.putInt(EXTRA_ACTION, action);
- instance.setArguments(arguments);
- return instance;
+ PrintErrorFragment fragment = new PrintErrorFragment();
+ fragment.setArguments(arguments);
+ return fragment;
}
@Override
@@ -63,12 +62,11 @@ public final class PrintErrorFragment extends Fragment {
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- Bundle arguments = getArguments();
+ CharSequence message = getArguments().getCharSequence(EXTRA_MESSAGE);
- CharSequence error = arguments.getString(EXTRA_ERROR_MESSAGE);
- if (!TextUtils.isEmpty(error)) {
- TextView message = (TextView) view.findViewById(R.id.message);
- message.setText(error);
+ if (!TextUtils.isEmpty(message)) {
+ TextView messageView = (TextView) view.findViewById(R.id.message);
+ messageView.setText(message);
}
Button actionButton = (Button) view.findViewById(R.id.action_button);
@@ -80,11 +78,6 @@ public final class PrintErrorFragment extends Fragment {
actionButton.setText(R.string.print_error_retry);
} break;
- case ACTION_CONFIRM: {
- actionButton.setVisibility(View.VISIBLE);
- actionButton.setText(android.R.string.ok);
- } break;
-
case ACTION_NONE: {
actionButton.setVisibility(View.GONE);
} break;
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
new file mode 100644
index 0000000..910818b
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package com.android.printspooler.ui;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.print.PageRange;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Margins;
+import android.print.PrintDocumentInfo;
+import android.support.v7.widget.OrientationHelper;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.support.v7.widget.RecyclerView.LayoutManager;
+import android.support.v7.widget.StaggeredGridLayoutManager;
+import android.view.View;
+import com.android.internal.os.SomeArgs;
+import com.android.printspooler.R;
+import com.android.printspooler.model.MutexFileProvider;
+import com.android.printspooler.widget.PrintContentView;
+import com.android.printspooler.widget.EmbeddedContentContainer;
+import com.android.printspooler.widget.PrintOptionsLayout;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
+
+class PrintPreviewController implements MutexFileProvider.OnReleaseRequestCallback,
+ PageAdapter.PreviewArea, EmbeddedContentContainer.OnSizeChangeListener {
+
+ private PrintActivity mActivity;
+
+ private final MutexFileProvider mFileProvider;
+
+ private final MyHandler mHandler;
+
+ private final PageAdapter mPageAdapter;
+
+ private final StaggeredGridLayoutManager mLayoutManger;
+
+ private PrintOptionsLayout mPrintOptionsLayout;
+
+ private final RecyclerView mRecyclerView;
+
+ private final PrintContentView mContentView;
+
+ private final EmbeddedContentContainer mEmbeddedContentContainer;
+
+ private final PreloadController mPreloadController;
+
+ private int mDocumentPageCount;
+
+ public PrintPreviewController(PrintActivity activity, MutexFileProvider fileProvider) {
+ mActivity = activity;
+ mHandler = new MyHandler(activity.getMainLooper());
+ mFileProvider = fileProvider;
+
+ mPrintOptionsLayout = (PrintOptionsLayout) activity.findViewById(R.id.options_container);
+ mPageAdapter = new PageAdapter(activity, activity, this);
+
+ final int columnCount = mActivity.getResources().getInteger(
+ R.integer.preview_page_per_row_count);
+
+ mLayoutManger = new StaggeredGridLayoutManager(columnCount, OrientationHelper.VERTICAL);
+ mRecyclerView = (RecyclerView) activity.findViewById(R.id.preview_content);
+ mRecyclerView.setLayoutManager(mLayoutManger);
+ mRecyclerView.setAdapter(mPageAdapter);
+ mPreloadController = new PreloadController(mRecyclerView);
+ mRecyclerView.setOnScrollListener(mPreloadController);
+
+ mContentView = (PrintContentView) activity.findViewById(R.id.options_content);
+ mEmbeddedContentContainer = (EmbeddedContentContainer) activity.findViewById(
+ R.id.embedded_content_container);
+ mEmbeddedContentContainer.setOnSizeChangeListener(this);
+ }
+
+ @Override
+ public void onSizeChanged(int width, int height) {
+ mPageAdapter.onPreviewAreaSizeChanged();
+ }
+
+ public boolean isOptionsOpened() {
+ return mContentView.isOptionsOpened();
+ }
+
+ public void closeOptions() {
+ mContentView.closeOptions();
+ }
+
+ public void setUiShown(boolean shown) {
+ if (shown) {
+ mRecyclerView.setVisibility(View.VISIBLE);
+ } else {
+ mRecyclerView.setVisibility(View.GONE);
+ }
+ }
+
+ public void onOrientationChanged() {
+ // Adjust the print option column count.
+ final int optionColumnCount = mActivity.getResources().getInteger(
+ R.integer.print_option_column_count);
+ mPrintOptionsLayout.setColumnCount(optionColumnCount);
+ mPageAdapter.onOrientationChanged();
+ }
+
+ public int getFilePageCount() {
+ return mPageAdapter.getFilePageCount();
+ }
+
+ public PageRange[] getSelectedPages() {
+ return mPageAdapter.getSelectedPages();
+ }
+
+ public PageRange[] getRequestedPages() {
+ return mPageAdapter.getRequestedPages();
+ }
+
+ public void onContentUpdated(boolean documentChanged, int documentPageCount,
+ PageRange[] writtenPages, PageRange[] selectedPages, MediaSize mediaSize,
+ Margins minMargins) {
+ boolean contentChanged = false;
+
+ if (documentChanged) {
+ contentChanged = true;
+ }
+
+ if (documentPageCount != mDocumentPageCount) {
+ mDocumentPageCount = documentPageCount;
+ contentChanged = true;
+ }
+
+ if (contentChanged) {
+ // If not closed, close as we start over.
+ if (mPageAdapter.isOpened()) {
+ Message operation = mHandler.obtainMessage(MyHandler.MSG_CLOSE);
+ mHandler.enqueueOperation(operation);
+ }
+ }
+
+ // The content changed. In this case we have to invalidate
+ // all rendered pages and reopen the file...
+ if (contentChanged && writtenPages != null) {
+ Message operation = mHandler.obtainMessage(MyHandler.MSG_OPEN);
+ mHandler.enqueueOperation(operation);
+ }
+
+ // Update the attributes before after closed to avoid flicker.
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = writtenPages;
+ args.arg2 = selectedPages;
+ args.arg3 = mediaSize;
+ args.arg4 = minMargins;
+ args.argi1 = documentPageCount;
+
+ Message operation = mHandler.obtainMessage(MyHandler.MSG_UPDATE, args);
+ mHandler.enqueueOperation(operation);
+
+ // If document changed and has pages we want to start preloading.
+ if (contentChanged && writtenPages != null) {
+ operation = mHandler.obtainMessage(MyHandler.MSG_START_PRELOAD);
+ mHandler.enqueueOperation(operation);
+ }
+ }
+
+ @Override
+ public void onReleaseRequested(final File file) {
+ // This is called from the async task's single threaded executor
+ // thread, i.e. not on the main thread - so post a message.
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // At this point the other end will write to the file, hence
+ // we have to close it and reopen after the write completes.
+ Message operation = mHandler.obtainMessage(MyHandler.MSG_CLOSE);
+ mHandler.enqueueOperation(operation);
+ }
+ });
+ }
+
+ public void destroy() {
+ mPageAdapter.destroy();
+ }
+
+ @Override
+ public int getWidth() {
+ return mEmbeddedContentContainer.getWidth();
+ }
+
+ @Override
+ public int getHeight() {
+ return mEmbeddedContentContainer.getHeight();
+ }
+
+ @Override
+ public void setColumnCount(int columnCount) {
+ mLayoutManger.setSpanCount(columnCount);
+ }
+
+ @Override
+ public void setPadding(int left, int top , int right, int bottom) {
+ mRecyclerView.setPadding(left, top, right, bottom);
+ }
+
+ private final class MyHandler extends Handler {
+ public static final int MSG_OPEN = 1;
+ public static final int MSG_CLOSE = 2;
+ public static final int MSG_DESTROY = 3;
+ public static final int MSG_UPDATE = 4;
+ public static final int MSG_START_PRELOAD = 5;
+
+ private boolean mAsyncOperationInProgress;
+
+ private final Runnable mOnAsyncOperationDoneCallback = new Runnable() {
+ @Override
+ public void run() {
+ mAsyncOperationInProgress = false;
+ handleNextOperation();
+ }
+ };
+
+ private final List<Message> mPendingOperations = new ArrayList<>();
+
+ public MyHandler(Looper looper) {
+ super(looper, null, false);
+ }
+
+ public void enqueueOperation(Message message) {
+ mPendingOperations.add(message);
+ handleNextOperation();
+ }
+
+ public void handleNextOperation() {
+ while (!mPendingOperations.isEmpty() && !mAsyncOperationInProgress) {
+ Message operation = mPendingOperations.remove(0);
+ handleMessage(operation);
+ }
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_OPEN: {
+ try {
+ File file = mFileProvider.acquireFile(PrintPreviewController.this);
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ mAsyncOperationInProgress = true;
+ mPageAdapter.open(pfd, new Runnable() {
+ @Override
+ public void run() {
+ if (mDocumentPageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
+ mDocumentPageCount = mPageAdapter.getFilePageCount();
+ mActivity.updateOptionsUi();
+ }
+ mOnAsyncOperationDoneCallback.run();
+ }
+ });
+ } catch (FileNotFoundException fnfe) {
+ /* ignore - file guaranteed to be there */
+ }
+ } break;
+
+ case MSG_CLOSE: {
+ mAsyncOperationInProgress = true;
+ mPageAdapter.close(new Runnable() {
+ @Override
+ public void run() {
+ mFileProvider.releaseFile();
+ mOnAsyncOperationDoneCallback.run();
+ }
+ });
+ } break;
+
+ case MSG_DESTROY: {
+ mPageAdapter.destroy();
+ handleNextOperation();
+ } break;
+
+ case MSG_UPDATE: {
+ SomeArgs args = (SomeArgs) message.obj;
+ PageRange[] writtenPages = (PageRange[]) args.arg1;
+ PageRange[] selectedPages = (PageRange[]) args.arg2;
+ MediaSize mediaSize = (MediaSize) args.arg3;
+ Margins margins = (Margins) args.arg4;
+ final int pageCount = args.argi1;
+ args.recycle();
+
+ mPageAdapter.update(writtenPages, selectedPages, pageCount,
+ mediaSize, margins);
+
+ } break;
+
+ case MSG_START_PRELOAD: {
+ mPreloadController.startPreloadContent();
+ } break;
+ }
+ }
+ }
+
+ private final class PreloadController implements RecyclerView.OnScrollListener {
+ private final RecyclerView mRecyclerView;
+
+ private int mOldScrollState;
+
+ public PreloadController(RecyclerView recyclerView) {
+ mRecyclerView = recyclerView;
+ mOldScrollState = mRecyclerView.getScrollState();
+ }
+
+ @Override
+ public void onScrollStateChanged(int state) {
+ switch (mOldScrollState) {
+ case RecyclerView.SCROLL_STATE_SETTLING: {
+ if (state == RecyclerView.SCROLL_STATE_IDLE
+ || state == RecyclerView.SCROLL_STATE_DRAGGING){
+ startPreloadContent();
+ }
+ } break;
+
+ case RecyclerView.SCROLL_STATE_IDLE:
+ case RecyclerView.SCROLL_STATE_DRAGGING: {
+ if (state == RecyclerView.SCROLL_STATE_SETTLING) {
+ stopPreloadContent();
+ }
+ } break;
+ }
+ mOldScrollState = state;
+ }
+
+ @Override
+ public void onScrolled(int dx, int dy) {
+ /* do nothing */
+ }
+
+ public void startPreloadContent() {
+ PageAdapter pageAdapter = (PageAdapter) mRecyclerView.getAdapter();
+
+ if (pageAdapter.isOpened()) {
+ PageRange shownPages = computeShownPages();
+ if (shownPages != null) {
+ pageAdapter.startPreloadContent(shownPages);
+ }
+ }
+ }
+
+ public void stopPreloadContent() {
+ PageAdapter pageAdapter = (PageAdapter) mRecyclerView.getAdapter();
+
+ if (pageAdapter.isOpened()) {
+ pageAdapter.stopPreloadContent();
+ }
+ }
+
+ private PageRange computeShownPages() {
+ final int childCount = mRecyclerView.getChildCount();
+ if (childCount > 0) {
+ LayoutManager layoutManager = mRecyclerView.getLayoutManager();
+
+ View firstChild = layoutManager.getChildAt(0);
+ ViewHolder firstHolder = mRecyclerView.getChildViewHolder(firstChild);
+
+ View lastChild = layoutManager.getChildAt(layoutManager.getChildCount() - 1);
+ ViewHolder lastHolder = mRecyclerView.getChildViewHolder(lastChild);
+
+ return new PageRange(firstHolder.getPosition(), lastHolder.getPosition());
+ }
+ return null;
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewFragment.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewFragment.java
deleted file mode 100644
index d68a6aa..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewFragment.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.android.printspooler.ui;
-
-import android.app.Fragment;
-
-public class PrintPreviewFragment extends Fragment {
-
- public static PrintPreviewFragment newInstance() {
- return new PrintPreviewFragment();
- }
-
- // TODO: Implement
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintProgressFragment.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintProgressFragment.java
index 96aa153..f05f8da 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintProgressFragment.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintProgressFragment.java
@@ -16,54 +16,24 @@
package com.android.printspooler.ui;
-import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.TextView;
import com.android.printspooler.R;
/**
* Fragment for showing a work in progress UI.
*/
public final class PrintProgressFragment extends Fragment {
-
- public interface OnCancelRequestListener {
- public void onCancelRequest();
- }
-
public static PrintProgressFragment newInstance() {
return new PrintProgressFragment();
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup root,
- Bundle savedInstanceState) {
+ public View onCreateView(LayoutInflater inflater, ViewGroup root, Bundle state) {
return inflater.inflate(R.layout.print_progress_fragment, root, false);
}
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- final Button cancelButton = (Button) view.findViewById(R.id.cancel_button);
- final TextView message = (TextView) view.findViewById(R.id.message);
-
- cancelButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- Activity activity = getActivity();
- if (activity instanceof OnCancelRequestListener) {
- ((OnCancelRequestListener) getActivity()).onCancelRequest();
- }
- cancelButton.setVisibility(View.GONE);
- message.setVisibility(View.VISIBLE);
- }
- });
- }
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
index 33b294f..2b317b3 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
@@ -17,6 +17,7 @@
package com.android.printspooler.util;
import android.print.PageRange;
+import android.print.PrintDocumentInfo;
import java.util.Arrays;
import java.util.Comparator;
@@ -40,13 +41,32 @@ public final class PageRangeUtils {
}
/**
+ * Gets whether page ranges contains a given page.
+ *
+ * @param pageRanges The page ranges.
+ * @param pageIndex The page for which to check.
+ * @return Whether the page is within the ranges.
+ */
+ public static boolean contains(PageRange[] pageRanges, int pageIndex) {
+ final int rangeCount = pageRanges.length;
+ for (int i = 0; i < rangeCount; i++) {
+ PageRange pageRange = pageRanges[i];
+ if (pageRange.contains(pageIndex)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Checks whether one page range array contains another one.
*
* @param ourRanges The container page ranges.
* @param otherRanges The contained page ranges.
+ * @param pageCount The total number of pages.
* @return Whether the container page ranges contains the contained ones.
*/
- public static boolean contains(PageRange[] ourRanges, PageRange[] otherRanges) {
+ public static boolean contains(PageRange[] ourRanges, PageRange[] otherRanges, int pageCount) {
if (ourRanges == null || otherRanges == null) {
return false;
}
@@ -55,6 +75,10 @@ public final class PageRangeUtils {
return true;
}
+ if (Arrays.equals(otherRanges, ALL_PAGES_RANGE)) {
+ otherRanges[0] = new PageRange(0, pageCount - 1);
+ }
+
ourRanges = normalize(ourRanges);
otherRanges = normalize(otherRanges);
@@ -77,10 +101,7 @@ public final class PageRangeUtils {
}
}
}
- if (otherRangeIdx < otherRangeCount) {
- return false;
- }
- return true;
+ return (otherRangeIdx >= otherRangeCount);
}
/**
@@ -95,28 +116,42 @@ public final class PageRangeUtils {
if (pageRanges == null) {
return null;
}
+
final int oldRangeCount = pageRanges.length;
if (oldRangeCount <= 1) {
return pageRanges;
}
+
Arrays.sort(pageRanges, sComparator);
+
int newRangeCount = 1;
for (int i = 0; i < oldRangeCount - 1; i++) {
- newRangeCount++;
PageRange currentRange = pageRanges[i];
PageRange nextRange = pageRanges[i + 1];
if (currentRange.getEnd() + 1 >= nextRange.getStart()) {
- newRangeCount--;
pageRanges[i] = null;
pageRanges[i + 1] = new PageRange(currentRange.getStart(),
Math.max(currentRange.getEnd(), nextRange.getEnd()));
+ } else {
+ newRangeCount++;
}
}
+
if (newRangeCount == oldRangeCount) {
return pageRanges;
}
- return Arrays.copyOfRange(pageRanges, oldRangeCount - newRangeCount,
- oldRangeCount);
+
+ int normalRangeIndex = 0;
+ PageRange[] normalRanges = new PageRange[newRangeCount];
+ for (int i = 0; i < oldRangeCount; i++) {
+ PageRange normalRange = pageRanges[i];
+ if (normalRange != null) {
+ normalRanges[normalRangeIndex] = normalRange;
+ normalRangeIndex++;
+ }
+ }
+
+ return normalRanges;
}
/**
@@ -146,14 +181,89 @@ public final class PageRangeUtils {
*/
public static int getNormalizedPageCount(PageRange[] pageRanges, int layoutPageCount) {
int pageCount = 0;
+ if (pageRanges != null) {
+ final int pageRangeCount = pageRanges.length;
+ for (int i = 0; i < pageRangeCount; i++) {
+ PageRange pageRange = pageRanges[i];
+ if (PageRange.ALL_PAGES.equals(pageRange)) {
+ return layoutPageCount;
+ }
+ pageCount += pageRange.getSize();
+ }
+ }
+ return pageCount;
+ }
+
+ public static PageRange asAbsoluteRange(PageRange pageRange, int pageCount) {
+ if (PageRange.ALL_PAGES.equals(pageRange)) {
+ return new PageRange(0, pageCount - 1);
+ }
+ return pageRange;
+ }
+
+ public static boolean isAllPages(PageRange[] pageRanges) {
final int pageRangeCount = pageRanges.length;
for (int i = 0; i < pageRangeCount; i++) {
PageRange pageRange = pageRanges[i];
- if (PageRange.ALL_PAGES.equals(pageRange)) {
- return layoutPageCount;
+ if (isAllPages(pageRange)) {
+ return true;
}
- pageCount += pageRange.getEnd() - pageRange.getStart() + 1;
}
- return pageCount;
+ return false;
+ }
+
+ public static boolean isAllPages(PageRange pageRange) {
+ return PageRange.ALL_PAGES.equals(pageRange);
+ }
+
+ public static boolean isAllPages(PageRange[] pageRanges, int pageCount) {
+ final int pageRangeCount = pageRanges.length;
+ for (int i = 0; i < pageRangeCount; i++) {
+ PageRange pageRange = pageRanges[i];
+ if (isAllPages(pageRange, pageCount)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isAllPages(PageRange pageRanges, int pageCount) {
+ return pageRanges.getStart() == 0 && pageRanges.getEnd() == pageCount - 1;
+ }
+
+ public static PageRange[] computePrintedPages(PageRange[] requestedPages,
+ PageRange[] writtenPages, int pageCount) {
+ // Adjust the print job pages based on what was requested and written.
+ // The cases are ordered in the most expected to the least expected
+ // with a special case first where the app does not know the page count
+ // so we ask for all to be written.
+ if (Arrays.equals(requestedPages, ALL_PAGES_RANGE)
+ && pageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
+ return ALL_PAGES_RANGE;
+ } else if (Arrays.equals(writtenPages, requestedPages)) {
+ // We got a document with exactly the pages we wanted. Hence,
+ // the printer has to print all pages in the data.
+ return ALL_PAGES_RANGE;
+ } else if (Arrays.equals(writtenPages, ALL_PAGES_RANGE)) {
+ // We requested specific pages but got all of them. Hence,
+ // the printer has to print only the requested pages.
+ return requestedPages;
+ } else if (PageRangeUtils.contains(writtenPages, requestedPages, pageCount)) {
+ // We requested specific pages and got more but not all pages.
+ // Hence, we have to offset appropriately the printed pages to
+ // be based off the start of the written ones instead of zero.
+ // The written pages are always non-null and not empty.
+ final int offset = -writtenPages[0].getStart();
+ PageRangeUtils.offset(requestedPages, offset);
+ return requestedPages;
+ } else if (Arrays.equals(requestedPages, ALL_PAGES_RANGE)
+ && isAllPages(writtenPages, pageCount)) {
+ // We requested all pages via the special constant and got all
+ // of them as an explicit enumeration. Hence, the printer has
+ // to print only the requested pages.
+ return ALL_PAGES_RANGE;
+ }
+
+ return null;
}
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/EmbeddedContentContainer.java b/packages/PrintSpooler/src/com/android/printspooler/widget/EmbeddedContentContainer.java
new file mode 100644
index 0000000..2afaa9b
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/EmbeddedContentContainer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package com.android.printspooler.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+public class EmbeddedContentContainer extends FrameLayout {
+ public interface OnSizeChangeListener {
+ public void onSizeChanged(int width, int height);
+ }
+
+ private OnSizeChangeListener mSizeChangeListener;
+
+ public EmbeddedContentContainer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setOnSizeChangeListener(OnSizeChangeListener listener) {
+ mSizeChangeListener = listener;
+ }
+
+ @Override
+ protected void onSizeChanged(int newWidth, int newHeight, int oldWidth, int oldHeight) {
+ super.onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
+ if (mSizeChangeListener != null) {
+ mSizeChangeListener.onSizeChanged(newWidth, newHeight);
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java
new file mode 100644
index 0000000..bb63fb8
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package com.android.printspooler.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Margins;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.View;
+import com.android.printspooler.model.PageContentRepository;
+import com.android.printspooler.model.PageContentRepository.PageContentProvider;
+import com.android.printspooler.model.PageContentRepository.RenderSpec;
+
+/**
+ * This class represents a page in the print preview list. The width of the page
+ * is determined by stretching it to take maximal horizontal space while the height
+ * is computed from the width using the page aspect ratio. Note that different media
+ * sizes have different aspect ratios.
+ */
+public class PageContentView extends View
+ implements PageContentRepository.OnPageContentAvailableCallback {
+
+ private final ColorDrawable mEmptyState;
+
+ private PageContentProvider mProvider;
+
+ private MediaSize mMediaSize;
+
+ private Margins mMinMargins;
+
+ private boolean mContentRequested;
+
+ public PageContentView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedValue typedValue = new TypedValue();
+ context.getTheme().resolveAttribute(com.android.internal.R.attr.textColorPrimary,
+ typedValue, true);
+
+ mEmptyState = new ColorDrawable(typedValue.data);
+
+ setBackground(mEmptyState);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ requestPageContentIfNeeded();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ requestPageContentIfNeeded();
+ }
+
+ @Override
+ public void onPageContentAvailable(BitmapDrawable content) {
+ if (getBackground() != content) {
+ setBackground(content);
+ }
+ }
+
+ public PageContentProvider getPageContentProvider() {
+ return mProvider;
+ }
+
+ public void init(PageContentProvider provider, MediaSize mediaSize, Margins minMargins) {
+ if (mProvider == provider
+ && ((mMediaSize == null) ? mediaSize == null : mMediaSize.equals(mediaSize))
+ && ((mMinMargins == null) ? minMargins == null : mMinMargins.equals(minMargins))) {
+ return;
+ }
+
+ mProvider = provider;
+ mMediaSize = mediaSize;
+ mMinMargins = minMargins;
+ mContentRequested = false;
+
+ // If there is not provider we want immediately to switch to
+ // the empty state, so pages with no content appear blank.
+ if (mProvider == null && getBackground() != mEmptyState) {
+ setBackground(mEmptyState);
+ }
+
+ requestPageContentIfNeeded();
+ }
+
+ private void requestPageContentIfNeeded() {
+ if (getWidth() > 0 && getHeight() > 0 && !mContentRequested && mProvider != null) {
+ mContentRequested = true;
+ mProvider.getPageContent(new RenderSpec(getWidth(), getHeight(), mMediaSize,
+ mMinMargins), this);
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/ContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
index 77ca541..b4a78e6 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/ContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
@@ -34,40 +34,55 @@ import com.android.printspooler.R;
* the former is opened.
*/
@SuppressWarnings("unused")
-public final class ContentView extends ViewGroup implements View.OnClickListener {
+public final class PrintContentView extends ViewGroup implements View.OnClickListener {
private static final int FIRST_POINTER_ID = 0;
private final ViewDragHelper mDragger;
+ private final int mScrimColor;
+
private View mStaticContent;
private ViewGroup mSummaryContent;
private View mDynamicContent;
private View mDraggableContent;
+ private View mPrintButton;
private ViewGroup mMoreOptionsContainer;
private ViewGroup mOptionsContainer;
private View mEmbeddedContentContainer;
+ private View mEmbeddedContentScrim;
private View mExpandCollapseHandle;
private View mExpandCollapseIcon;
private int mClosedOptionsOffsetY;
- private int mCurrentOptionsOffsetY;
+ private int mCurrentOptionsOffsetY = Integer.MIN_VALUE;
private OptionsStateChangeListener mOptionsStateChangeListener;
+ private OptionsStateController mOptionsStateController;
+
private int mOldDraggableHeight;
+ private float mDragProgress;
+
public interface OptionsStateChangeListener {
public void onOptionsOpened();
public void onOptionsClosed();
}
- public ContentView(Context context, AttributeSet attrs) {
+ public interface OptionsStateController {
+ public boolean canOpenOptions();
+ public boolean canCloseOptions();
+ }
+
+ public PrintContentView(Context context, AttributeSet attrs) {
super(context, attrs);
mDragger = ViewDragHelper.create(this, new DragCallbacks());
+ mScrimColor = context.getResources().getColor(R.color.print_preview_scrim_color);
+
// The options view is sliding under the static header but appears
// after it in the layout, so we will draw in opposite order.
setChildrenDrawingOrderEnabled(true);
@@ -77,7 +92,11 @@ public final class ContentView extends ViewGroup implements View.OnClickListener
mOptionsStateChangeListener = listener;
}
- private boolean isOptionsOpened() {
+ public void setOpenOptionsController(OptionsStateController controller) {
+ mOptionsStateController = controller;
+ }
+
+ public boolean isOptionsOpened() {
return mCurrentOptionsOffsetY == 0;
}
@@ -85,7 +104,7 @@ public final class ContentView extends ViewGroup implements View.OnClickListener
return mCurrentOptionsOffsetY == mClosedOptionsOffsetY;
}
- private void openOptions() {
+ public void openOptions() {
if (isOptionsOpened()) {
return;
}
@@ -94,7 +113,7 @@ public final class ContentView extends ViewGroup implements View.OnClickListener
invalidate();
}
- private void closeOptions() {
+ public void closeOptions() {
if (isOptionsClosed()) {
return;
}
@@ -114,13 +133,14 @@ public final class ContentView extends ViewGroup implements View.OnClickListener
mSummaryContent = (ViewGroup) findViewById(R.id.summary_content);
mDynamicContent = findViewById(R.id.dynamic_content);
mDraggableContent = findViewById(R.id.draggable_content);
+ mPrintButton = findViewById(R.id.print_button);
mMoreOptionsContainer = (ViewGroup) findViewById(R.id.more_options_container);
mOptionsContainer = (ViewGroup) findViewById(R.id.options_container);
mEmbeddedContentContainer = findViewById(R.id.embedded_content_container);
- mExpandCollapseIcon = findViewById(R.id.expand_collapse_icon);
+ mEmbeddedContentScrim = findViewById(R.id.embedded_content_scrim);
mExpandCollapseHandle = findViewById(R.id.expand_collapse_handle);
+ mExpandCollapseIcon = findViewById(R.id.expand_collapse_icon);
- mExpandCollapseIcon.setOnClickListener(this);
mExpandCollapseHandle.setOnClickListener(this);
// Make sure we start in a closed options state.
@@ -129,12 +149,16 @@ public final class ContentView extends ViewGroup implements View.OnClickListener
@Override
public void onClick(View view) {
- if (view == mExpandCollapseHandle || view == mExpandCollapseIcon) {
- if (isOptionsClosed()) {
+ if (view == mExpandCollapseHandle) {
+ if (isOptionsClosed() && mOptionsStateController.canOpenOptions()) {
openOptions();
- } else if (isOptionsOpened()) {
+ } else if (isOptionsOpened() && mOptionsStateController.canCloseOptions()) {
closeOptions();
} // else in open/close progress do nothing.
+ } else if (view == mEmbeddedContentScrim) {
+ if (isOptionsOpened() && mOptionsStateController.canCloseOptions()) {
+ closeOptions();
+ }
}
}
@@ -162,6 +186,12 @@ public final class ContentView extends ViewGroup implements View.OnClickListener
}
}
+ private int computeScrimColor() {
+ final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
+ final int adjustedAlpha = (int) (baseAlpha * (1 - mDragProgress));
+ return adjustedAlpha << 24 | (mScrimColor & 0xffffff);
+ }
+
private int getOpenedOptionsY() {
return mStaticContent.getBottom();
}
@@ -172,6 +202,8 @@ public final class ContentView extends ViewGroup implements View.OnClickListener
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final boolean wasOpened = isOptionsOpened();
+
measureChild(mStaticContent, widthMeasureSpec, heightMeasureSpec);
if (mSummaryContent.getVisibility() != View.GONE) {
@@ -180,34 +212,34 @@ public final class ContentView extends ViewGroup implements View.OnClickListener
measureChild(mDynamicContent, widthMeasureSpec, heightMeasureSpec);
- final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-
-// // The height of the draggable content may change and if that happens
-// // we have to adjust the current offset to ensure the sliding area is
-// // at the same position.
-// mCurrentOptionsOffsetY -= mDraggableContent.getMeasuredHeight()
-// - oldDraggableHeight;
-
- if (mOldDraggableHeight != mDraggableContent.getMeasuredHeight()) {
- mCurrentOptionsOffsetY -= mDraggableContent.getMeasuredHeight()
- - mOldDraggableHeight;
- mOldDraggableHeight = mDraggableContent.getMeasuredHeight();
- }
+ measureChild(mPrintButton, widthMeasureSpec, heightMeasureSpec);
// The height of the draggable content may change and if that happens
// we have to adjust the sliding area closed state offset.
mClosedOptionsOffsetY = mSummaryContent.getMeasuredHeight()
- mDraggableContent.getMeasuredHeight();
+ if (mCurrentOptionsOffsetY == Integer.MIN_VALUE) {
+ mCurrentOptionsOffsetY = mClosedOptionsOffsetY;
+ }
+
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
// The content host must be maximally large size that fits entirely
// on the screen when the options are collapsed.
ViewGroup.LayoutParams params = mEmbeddedContentContainer.getLayoutParams();
- if (params.height == 0) {
- params.height = heightSize - mStaticContent.getMeasuredHeight()
- - mSummaryContent.getMeasuredHeight() - mDynamicContent.getMeasuredHeight()
- + mDraggableContent.getMeasuredHeight();
+ params.height = heightSize - mStaticContent.getMeasuredHeight()
+ - mSummaryContent.getMeasuredHeight() - mDynamicContent.getMeasuredHeight()
+ + mDraggableContent.getMeasuredHeight();
- mCurrentOptionsOffsetY = mClosedOptionsOffsetY;
+ // The height of the draggable content may change and if that happens
+ // we have to adjust the current offset to ensure the sliding area is
+ // at the correct position.
+ if (mOldDraggableHeight != mDraggableContent.getMeasuredHeight()) {
+ if (mOldDraggableHeight != 0) {
+ mCurrentOptionsOffsetY = wasOpened ? 0 : mClosedOptionsOffsetY;
+ }
+ mOldDraggableHeight = mDraggableContent.getMeasuredHeight();
}
// The content host can grow vertically as much as needed - we will be covering it.
@@ -232,6 +264,15 @@ public final class ContentView extends ViewGroup implements View.OnClickListener
mDynamicContent.layout(left, dynContentTop, right, dynContentBottom);
+ MarginLayoutParams params = (MarginLayoutParams) mPrintButton.getLayoutParams();
+ final int rightMargin = params.rightMargin;
+ final int printButtonLeft = right - mPrintButton.getMeasuredWidth() - rightMargin;
+ final int printButtonTop = dynContentBottom - mPrintButton.getMeasuredHeight() / 2;
+ final int printButtonRight = printButtonLeft + mPrintButton.getMeasuredWidth();
+ final int printButtonBottom = printButtonTop + mPrintButton.getMeasuredHeight();
+
+ mPrintButton.layout(printButtonLeft, printButtonTop, printButtonRight, printButtonBottom);
+
final int embContentTop = mStaticContent.getMeasuredHeight() + mClosedOptionsOffsetY
+ mDynamicContent.getMeasuredHeight();
final int embContentBottom = embContentTop + mEmbeddedContentContainer.getMeasuredHeight();
@@ -239,40 +280,51 @@ public final class ContentView extends ViewGroup implements View.OnClickListener
mEmbeddedContentContainer.layout(left, embContentTop, right, embContentBottom);
}
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new ViewGroup.MarginLayoutParams(getContext(), attrs);
+ }
+
private void onDragProgress(float progress) {
- final int summaryCount = mSummaryContent.getChildCount();
- for (int i = 0; i < summaryCount; i++) {
- View child = mSummaryContent.getChildAt(i);
- child.setAlpha(progress);
+ if (Float.compare(mDragProgress, progress) == 0) {
+ return;
}
+ if ((mDragProgress == 0 && progress > 0)
+ || (mDragProgress == 1.0f && progress < 1.0f)) {
+ mSummaryContent.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ mDraggableContent.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ mMoreOptionsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+ if ((mDragProgress > 0 && progress == 0)
+ || (mDragProgress < 1.0f && progress == 1.0f)) {
+ mSummaryContent.setLayerType(View.LAYER_TYPE_NONE, null);
+ mDraggableContent.setLayerType(View.LAYER_TYPE_NONE, null);
+ mMoreOptionsContainer.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+
+ mDragProgress = progress;
+
+ mSummaryContent.setAlpha(progress);
+
+ final float inverseAlpha = 1.0f - progress;
+ mOptionsContainer.setAlpha(inverseAlpha);
+ mMoreOptionsContainer.setAlpha(inverseAlpha);
+
+ mEmbeddedContentScrim.setBackgroundColor(computeScrimColor());
+
if (progress == 0) {
if (mOptionsStateChangeListener != null) {
mOptionsStateChangeListener.onOptionsOpened();
}
mSummaryContent.setVisibility(View.GONE);
+ mEmbeddedContentScrim.setOnClickListener(this);
mExpandCollapseIcon.setBackgroundResource(R.drawable.ic_expand_less);
} else {
mSummaryContent.setVisibility(View.VISIBLE);
}
- final float inverseAlpha = 1.0f - progress;
-
- final int optionCount = mOptionsContainer.getChildCount();
- for (int i = 0; i < optionCount; i++) {
- View child = mOptionsContainer.getChildAt(i);
- child.setAlpha(inverseAlpha);
- }
-
- if (mMoreOptionsContainer.getVisibility() != View.GONE) {
- final int moreOptionCount = mMoreOptionsContainer.getChildCount();
- for (int i = 0; i < moreOptionCount; i++) {
- View child = mMoreOptionsContainer.getChildAt(i);
- child.setAlpha(inverseAlpha);
- }
- }
-
- if (inverseAlpha == 0) {
+ if (progress == 1.0f) {
if (mOptionsStateChangeListener != null) {
mOptionsStateChangeListener.onOptionsClosed();
}
@@ -280,6 +332,10 @@ public final class ContentView extends ViewGroup implements View.OnClickListener
mMoreOptionsContainer.setVisibility(View.INVISIBLE);
}
mDraggableContent.setVisibility(View.INVISIBLE);
+ // If we change the scrim visibility the dimming is lagging
+ // and is janky. Now it is there but transparent, doing nothing.
+ mEmbeddedContentScrim.setOnClickListener(null);
+ mEmbeddedContentScrim.setClickable(false);
mExpandCollapseIcon.setBackgroundResource(R.drawable.ic_expand_more);
} else {
if (mMoreOptionsContainer.getVisibility() != View.GONE) {
@@ -292,15 +348,25 @@ public final class ContentView extends ViewGroup implements View.OnClickListener
private final class DragCallbacks extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
+ if (isOptionsOpened() && !mOptionsStateController.canCloseOptions()
+ || isOptionsClosed() && !mOptionsStateController.canOpenOptions()) {
+ return false;
+ }
return child == mDynamicContent && pointerId == FIRST_POINTER_ID;
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+ if ((isOptionsClosed() || isOptionsClosed()) && dy <= 0) {
+ return;
+ }
+
mCurrentOptionsOffsetY += dy;
final float progress = ((float) top - getOpenedOptionsY())
/ (getClosedOptionsY() - getOpenedOptionsY());
+ mPrintButton.offsetTopAndBottom(dy);
+
mDraggableContent.notifySubtreeAccessibilityStateChangedIfNeeded();
onDragProgress(progress);
@@ -326,6 +392,10 @@ public final class ContentView extends ViewGroup implements View.OnClickListener
invalidate();
}
+ public int getOrderedChildIndex(int index) {
+ return getChildCount() - index - 1;
+ }
+
public int getViewVerticalDragRange(View child) {
return mDraggableContent.getHeight();
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintOptionsLayout.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintOptionsLayout.java
index 23c8d08..01f4a04 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintOptionsLayout.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintOptionsLayout.java
@@ -31,7 +31,7 @@ import com.android.printspooler.R;
@SuppressWarnings("unused")
public final class PrintOptionsLayout extends ViewGroup {
- private final int mColumnCount;
+ private int mColumnCount;
public PrintOptionsLayout(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -42,6 +42,13 @@ public final class PrintOptionsLayout extends ViewGroup {
typedArray.recycle();
}
+ public void setColumnCount(int columnCount) {
+ if (mColumnCount != columnCount) {
+ mColumnCount = columnCount;
+ requestLayout();
+ }
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);