summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk2
-rw-r--r--CleanSpec.mk2
-rw-r--r--api/current.txt59
-rw-r--r--core/java/android/app/AlarmManager.java10
-rw-r--r--core/java/android/ddm/DdmHandleHello.java4
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java3
-rw-r--r--core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl4
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDevice.java20
-rw-r--r--core/java/android/net/LinkProperties.java64
-rw-r--r--core/java/android/net/MobileDataStateTracker.java2
-rw-r--r--core/java/android/os/Process.java18
-rw-r--r--core/java/android/print/FileDocumentAdapter.java8
-rw-r--r--core/java/android/print/ILayoutResultCallback.aidl6
-rw-r--r--core/java/android/print/IPrintDocumentAdapter.aidl6
-rw-r--r--core/java/android/print/IPrintManager.aidl1
-rw-r--r--core/java/android/print/IPrintSpoolerCallbacks.aidl2
-rw-r--r--core/java/android/print/IPrintSpoolerClient.aidl2
-rw-r--r--core/java/android/print/IPrintSpoolerObserver.aidl31
-rw-r--r--core/java/android/print/IPrinterDiscoveryObserver.aidl5
-rw-r--r--core/java/android/print/IWriteResultCallback.aidl6
-rw-r--r--core/java/android/print/PageRange.java32
-rw-r--r--core/java/android/print/PrintAttributes.java295
-rw-r--r--core/java/android/print/PrintDocumentAdapter.java30
-rw-r--r--core/java/android/print/PrintDocumentInfo.java30
-rw-r--r--core/java/android/print/PrintJob.java13
-rw-r--r--core/java/android/print/PrintJobInfo.java42
-rw-r--r--core/java/android/print/PrintManager.java216
-rw-r--r--core/java/android/print/PrinterId.java3
-rw-r--r--core/java/android/print/PrinterInfo.java341
-rw-r--r--core/java/android/printservice/IPrintService.aidl7
-rw-r--r--core/java/android/printservice/PrintJob.java12
-rw-r--r--core/java/android/printservice/PrintService.java138
-rw-r--r--core/java/android/provider/DocumentsContract.java138
-rw-r--r--core/java/android/util/MapCollections.java73
-rw-r--r--core/java/android/view/inputmethod/InputMethodInfo.java39
-rw-r--r--core/java/android/view/inputmethod/InputMethodSubtype.java233
-rw-r--r--core/java/android/webkit/WebView.java1
-rw-r--r--core/java/android/webkit/WebViewFactory.java2
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodUtils.java39
-rw-r--r--core/jni/android_util_AssetManager.cpp34
-rw-r--r--core/jni/android_util_Process.cpp27
-rw-r--r--core/res/res/values/attrs.xml5
-rw-r--r--core/res/res/values/public.xml1
-rw-r--r--core/tests/coretests/src/android/net/LinkPropertiesTest.java27
-rw-r--r--docs/html/_redirects.yaml3
-rw-r--r--docs/html/about/versions/jelly-bean.jd9
-rw-r--r--docs/html/channels/io2013.jd10
-rw-r--r--docs/html/design/downloads/index.jd8
-rw-r--r--docs/html/design/index.jd8
-rw-r--r--docs/html/develop/index.jd10
-rw-r--r--docs/html/distribute/distribute_toc.cs1
-rw-r--r--docs/html/distribute/googleplay/promote/badges.jd8
-rw-r--r--docs/html/distribute/googleplay/promote/brand.jd9
-rw-r--r--docs/html/distribute/googleplay/promote/index.jd8
-rw-r--r--docs/html/distribute/googleplay/promote/linking.jd8
-rw-r--r--docs/html/distribute/googleplay/publish/preparing.jd8
-rw-r--r--docs/html/distribute/googleplay/quality/core.jd8
-rw-r--r--docs/html/distribute/googleplay/quality/index.jd8
-rw-r--r--docs/html/distribute/googleplay/quality/tablet.jd10
-rw-r--r--docs/html/distribute/googleplay/spotlight/games.jd245
-rw-r--r--docs/html/distribute/googleplay/spotlight/index.jd9
-rw-r--r--docs/html/distribute/index.jd8
-rw-r--r--docs/html/distribute/promote/device-art.jd96
-rw-r--r--docs/html/google/index.jd8
-rw-r--r--docs/html/google/play-services/auth.jd2
-rw-r--r--docs/html/google/play/billing/gp-purchase-status-api.jd15
-rw-r--r--docs/html/google/play/billing/index.jd8
-rw-r--r--docs/html/guide/components/index.jd8
-rw-r--r--docs/html/guide/guide_toc.cs3
-rw-r--r--docs/html/guide/topics/ui/declaring-layout.jd17
-rw-r--r--docs/html/guide/topics/ui/layout/tabs.jd219
-rw-r--r--docs/html/images/distribute/concrete-pbc-gpgames.jpgbin0 -> 222819 bytes
-rw-r--r--docs/html/images/distribute/glu-ew-gpgames.jpgbin0 -> 263114 bytes
-rw-r--r--docs/html/images/distribute/vector-unit-rt-gpgames.jpgbin0 -> 175179 bytes
-rw-r--r--docs/html/index.jd8
-rw-r--r--docs/html/sdk/index.jd54
-rw-r--r--docs/html/sdk/installing/installing-adt.jd8
-rw-r--r--docs/html/tools/extras/oem-usb.jd2
-rw-r--r--docs/html/tools/index.jd9
-rw-r--r--docs/html/tools/sdk/eclipse-adt.jd226
-rw-r--r--docs/html/tools/sdk/ndk/index.jd2
-rw-r--r--docs/html/tools/sdk/tools-notes.jd92
-rw-r--r--docs/html/tools/support-library/features.jd49
-rw-r--r--docs/html/tools/support-library/index.jd24
-rw-r--r--docs/html/training/basics/intents/sending.jd23
-rw-r--r--docs/html/training/displaying-bitmaps/cache-bitmap.jd6
-rw-r--r--docs/html/training/index.jd9
-rw-r--r--graphics/java/android/graphics/Color.java18
-rw-r--r--graphics/java/android/graphics/Paint.java10
-rw-r--r--graphics/java/android/graphics/drawable/InsetDrawable.java7
-rw-r--r--include/androidfw/ResourceTypes.h14
-rw-r--r--libs/androidfw/ResourceTypes.cpp340
-rw-r--r--libs/hwui/Caches.cpp11
-rw-r--r--libs/hwui/FontRenderer.cpp297
-rw-r--r--libs/hwui/FontRenderer.h50
-rw-r--r--libs/hwui/GammaFontRenderer.h14
-rw-r--r--libs/hwui/OpenGLRenderer.cpp69
-rw-r--r--libs/hwui/PixelBuffer.h21
-rw-r--r--libs/hwui/font/CacheTexture.cpp37
-rw-r--r--libs/hwui/font/CacheTexture.h13
-rw-r--r--libs/hwui/font/FontUtil.h3
-rw-r--r--media/java/android/media/AudioManager.java38
-rw-r--r--media/java/android/media/AudioService.java4
-rw-r--r--media/java/android/media/FocusRequester.java65
-rw-r--r--media/java/android/media/IAudioService.aidl2
-rw-r--r--media/java/android/media/MediaFocusControl.java36
-rw-r--r--packages/DocumentsUI/Android.mk2
-rw-r--r--packages/DocumentsUI/res/drawable-hdpi/drawer_shadow.9.pngbin0 -> 171 bytes
-rw-r--r--packages/DocumentsUI/res/drawable-hdpi/ic_drawer.pngbin0 -> 2842 bytes
-rw-r--r--packages/DocumentsUI/res/drawable-hdpi/ic_menu_search.pngbin0 -> 1653 bytes
-rw-r--r--packages/DocumentsUI/res/layout/activity.xml38
-rw-r--r--packages/DocumentsUI/res/layout/dialog_create_dir.xml27
-rw-r--r--packages/DocumentsUI/res/layout/item_backend.xml58
-rw-r--r--packages/DocumentsUI/res/layout/item_root.xml60
-rw-r--r--packages/DocumentsUI/res/layout/item_title.xml41
-rw-r--r--packages/DocumentsUI/res/menu/activity.xml9
-rw-r--r--packages/DocumentsUI/res/menu/directory.xml5
-rw-r--r--packages/DocumentsUI/res/values/strings.xml12
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java119
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java59
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java403
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java2
-rw-r--r--packages/ExternalStorageProvider/res/values/strings.xml1
-rw-r--r--packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java246
-rw-r--r--packages/PrintSpooler/res/layout/print_job_config_activity.xml28
-rw-r--r--packages/PrintSpooler/res/values/strings.xml7
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java1293
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java394
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java5
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java480
-rw-r--r--services/java/com/android/server/AlarmManagerService.java38
-rw-r--r--services/java/com/android/server/ConnectivityService.java6
-rw-r--r--services/java/com/android/server/InputMethodManagerService.java24
-rw-r--r--services/java/com/android/server/NotificationManagerService.java16
-rw-r--r--services/java/com/android/server/print/RemotePrintService.java123
-rw-r--r--services/java/com/android/server/print/RemotePrintSpooler.java94
-rw-r--r--services/java/com/android/server/print/UserState.java16
-rw-r--r--tests/ActivityTests/src/com/google/android/test/activity/ArrayMapTests.java59
138 files changed, 5187 insertions, 2826 deletions
diff --git a/Android.mk b/Android.mk
index 74cf8244223e..9adf4341912a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -623,7 +623,7 @@ sample_dir := development/samples
## SDK version identifiers used in the published docs
# major[.minor] version for current SDK. (full releases only)
-framework_docs_SDK_VERSION:=4.2
+framework_docs_SDK_VERSION:=4.3
# release version (ie "Release x") (full releases only)
framework_docs_SDK_REL_ID:=1
diff --git a/CleanSpec.mk b/CleanSpec.mk
index c696aa1db290..73e8fa4bc171 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -165,6 +165,8 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framew
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/framework-res_intermediates)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates)
$(call add-clean-step, rm -rf $(HOST_OUT)/obj/STATIC_LIBRARIES/libandroidfw_intermediates/import_includes)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
diff --git a/api/current.txt b/api/current.txt
index 46b042b85564..620e5412d99d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -605,6 +605,7 @@ package android {
field public static final int installLocation = 16843447; // 0x10102b7
field public static final int interpolator = 16843073; // 0x1010141
field public static final int isAlwaysSyncable = 16843571; // 0x1010333
+ field public static final int isAsciiCapable = 16843750; // 0x10103e6
field public static final int isAuxiliary = 16843647; // 0x101037f
field public static final int isDefault = 16843297; // 0x1010221
field public static final int isIndicator = 16843079; // 0x1010147
@@ -9342,6 +9343,7 @@ package android.graphics {
field public static final int ANTI_ALIAS_FLAG = 1; // 0x1
field public static final int DEV_KERN_TEXT_FLAG = 256; // 0x100
field public static final int DITHER_FLAG = 4; // 0x4
+ field public static final int EMBEDDED_BITMAP_TEXT_FLAG = 1024; // 0x400
field public static final int FAKE_BOLD_TEXT_FLAG = 32; // 0x20
field public static final int FILTER_BITMAP_FLAG = 2; // 0x2
field public static final int HINTING_OFF = 0; // 0x0
@@ -10077,6 +10079,7 @@ package android.graphics.drawable {
ctor public InsetDrawable(android.graphics.drawable.Drawable, int);
ctor public InsetDrawable(android.graphics.drawable.Drawable, int, int, int, int);
method public void draw(android.graphics.Canvas);
+ method public android.graphics.drawable.Drawable getDrawable();
method public int getOpacity();
method public void invalidateDrawable(android.graphics.drawable.Drawable);
method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
@@ -11648,6 +11651,7 @@ package android.media {
field public static final int ADJUST_SAME = 0; // 0x0
field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4; // 0x4
field public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3; // 0x3
field public static final int AUDIOFOCUS_LOSS = -1; // 0xffffffff
field public static final int AUDIOFOCUS_LOSS_TRANSIENT = -2; // 0xfffffffe
@@ -18542,7 +18546,6 @@ package android.print {
method public void clear();
method public int describeContents();
method public int getColorMode();
- method public int getCopies();
method public int getDuplexMode();
method public int getFittingMode();
method public android.print.PrintAttributes.Tray getInputTray();
@@ -18568,7 +18571,6 @@ package android.print {
ctor public PrintAttributes.Builder();
method public android.print.PrintAttributes create();
method public android.print.PrintAttributes.Builder setColorMode(int);
- method public android.print.PrintAttributes.Builder setCopyCount(int);
method public android.print.PrintAttributes.Builder setDuplexMode(int);
method public android.print.PrintAttributes.Builder setFittingMode(int);
method public android.print.PrintAttributes.Builder setInputTray(android.print.PrintAttributes.Tray);
@@ -18654,18 +18656,20 @@ package android.print {
method public void onFinish();
method public abstract void onLayout(android.print.PrintAttributes, android.print.PrintAttributes, android.os.CancellationSignal, android.print.PrintDocumentAdapter.LayoutResultCallback, android.os.Bundle);
method public void onStart();
- method public abstract void onWrite(java.util.List<android.print.PageRange>, java.io.FileDescriptor, android.os.CancellationSignal, android.print.PrintDocumentAdapter.WriteResultCallback);
+ method public abstract void onWrite(android.print.PageRange[], java.io.FileDescriptor, android.os.CancellationSignal, android.print.PrintDocumentAdapter.WriteResultCallback);
field public static final java.lang.String METADATA_KEY_PRINT_PREVIEW = "KEY_METADATA_PRINT_PREVIEW";
}
public static abstract class PrintDocumentAdapter.LayoutResultCallback {
+ method public void onLayoutCancelled();
method public void onLayoutFailed(java.lang.CharSequence);
method public void onLayoutFinished(android.print.PrintDocumentInfo, boolean);
}
public static abstract class PrintDocumentAdapter.WriteResultCallback {
+ method public void onWriteCancelled();
method public void onWriteFailed(java.lang.CharSequence);
- method public void onWriteFinished(java.util.List<android.print.PageRange>);
+ method public void onWriteFinished(android.print.PageRange[]);
}
public final class PrintDocumentInfo implements android.os.Parcelable {
@@ -18696,6 +18700,7 @@ package android.print {
public final class PrintJobInfo implements android.os.Parcelable {
method public int describeContents();
method public android.print.PrintAttributes getAttributes();
+ method public int getCopies();
method public int getId();
method public java.lang.CharSequence getLabel();
method public android.print.PageRange[] getPages();
@@ -18740,6 +18745,7 @@ package android.print {
method public java.util.List<android.print.PrintAttributes.Tray> getOutputTrays();
method public java.util.List<android.print.PrintAttributes.Resolution> getResolutions();
method public int getStatus();
+ method public boolean hasAllRequiredAttributes();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
field public static final int STATUS_READY = 1; // 0x1
@@ -18825,9 +18831,11 @@ package android.printservice {
method protected void onDisconnected();
method protected abstract void onPrintJobQueued(android.printservice.PrintJob);
method protected void onRequestCancelPrintJob(android.printservice.PrintJob);
+ method protected void onRequestUpdatePrinters(java.util.List<android.print.PrinterId>);
method protected abstract void onStartPrinterDiscovery();
method protected abstract void onStopPrinterDiscovery();
method public final void removeDiscoveredPrinters(java.util.List<android.print.PrinterId>);
+ method public final void updateDiscoveredPrinters(java.util.List<android.print.PrinterInfo>);
field public static final java.lang.String SERVICE_INTERFACE = "android.printservice.PrintService";
field public static final java.lang.String SERVICE_META_DATA = "android.printservice";
}
@@ -20259,15 +20267,25 @@ package android.provider {
ctor public DocumentsContract();
method public static android.net.Uri buildContentsUri(android.net.Uri);
method public static android.net.Uri buildDocumentUri(java.lang.String, java.lang.String);
- method public static android.net.Uri buildSearchUri(java.lang.String, java.lang.String);
+ method public static android.net.Uri buildRootsUri(java.lang.String);
+ method public static android.net.Uri buildSearchUri(android.net.Uri, java.lang.String);
method public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point);
method public static boolean renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String);
+ field public static final java.lang.String EXTRA_HAS_MORE = "has_more";
+ field public static final java.lang.String EXTRA_REQUEST_MORE = "request_more";
field public static final java.lang.String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
+ field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4
field public static final int FLAG_SUPPORTS_RENAME = 2; // 0x2
- field public static final int FLAG_SUPPORTS_THUMBNAIL = 4; // 0x4
+ field public static final int FLAG_SUPPORTS_SEARCH = 16; // 0x10
+ field public static final int FLAG_SUPPORTS_THUMBNAIL = 8; // 0x8
field public static final java.lang.String MIME_TYPE_DIRECTORY = "vnd.android.cursor.dir/doc";
+ field public static final java.lang.String PARAM_QUERY = "query";
field public static final java.lang.String ROOT_GUID = "0";
+ field public static final int ROOT_TYPE_DEVICE = 3; // 0x3
+ field public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; // 0x4
+ field public static final int ROOT_TYPE_SERVICE = 1; // 0x1
+ field public static final int ROOT_TYPE_SHORTCUT = 2; // 0x2
}
public static abstract interface DocumentsContract.DocumentColumns implements android.provider.OpenableColumns {
@@ -20277,6 +20295,15 @@ package android.provider {
field public static final java.lang.String MIME_TYPE = "mime_type";
}
+ public static abstract interface DocumentsContract.RootColumns {
+ field public static final java.lang.String AVAILABLE_BYTES = "available_bytes";
+ field public static final java.lang.String GUID = "guid";
+ field public static final java.lang.String ICON = "icon";
+ field public static final java.lang.String ROOT_TYPE = "root_type";
+ field public static final java.lang.String SUMMARY = "summary";
+ field public static final java.lang.String TITLE = "title";
+ }
+
public final deprecated class LiveFolders implements android.provider.BaseColumns {
field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
field public static final java.lang.String DESCRIPTION = "description";
@@ -28885,8 +28912,8 @@ package android.view.inputmethod {
}
public final class InputMethodSubtype implements android.os.Parcelable {
- ctor public InputMethodSubtype(int, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean);
- ctor public InputMethodSubtype(int, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean, int);
+ ctor public deprecated InputMethodSubtype(int, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean);
+ ctor public deprecated InputMethodSubtype(int, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean, int);
method public boolean containsExtraValueKey(java.lang.String);
method public int describeContents();
method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
@@ -28896,12 +28923,27 @@ package android.view.inputmethod {
method public java.lang.String getLocale();
method public java.lang.String getMode();
method public int getNameResId();
+ method public boolean isAsciiCapable();
method public boolean isAuxiliary();
method public boolean overridesImplicitlyEnabledSubtype();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
}
+ public static class InputMethodSubtype.InputMethodSubtypeBuilder {
+ ctor public InputMethodSubtype.InputMethodSubtypeBuilder();
+ method public android.view.inputmethod.InputMethodSubtype build();
+ method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean);
+ method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean);
+ method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean);
+ method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(java.lang.String);
+ method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int);
+ method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeId(int);
+ method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeLocale(java.lang.String);
+ method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeMode(java.lang.String);
+ method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeNameResId(int);
+ }
+
}
package android.view.textservice {
@@ -29535,6 +29577,7 @@ package android.webkit {
method public android.webkit.WebBackForwardList copyBackForwardList();
method public void destroy();
method public void documentHasImages(android.os.Message);
+ method public void evaluateJavascript(java.lang.String, android.webkit.ValueCallback<java.lang.String>);
method public static java.lang.String findAddress(java.lang.String);
method public deprecated int findAll(java.lang.String);
method public void findAllAsync(java.lang.String);
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index d9c377545499..b73fbfffb453 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -257,6 +257,16 @@ public class AlarmManager
private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
PendingIntent operation) {
+ if (triggerAtMillis < 0) {
+ /* NOTYET
+ if (mAlwaysExact) {
+ // Fatal error for KLP+ apps to use negative trigger times
+ throw new IllegalArgumentException("Invalid alarm trigger time "
+ + triggerAtMillis);
+ }
+ */
+ triggerAtMillis = 0;
+ }
try {
mService.set(type, triggerAtMillis, windowMillis, intervalMillis, operation);
} catch (RemoteException ex) {
diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java
index 842a4823f85b..220b40d64b3d 100644
--- a/core/java/android/ddm/DdmHandleHello.java
+++ b/core/java/android/ddm/DdmHandleHello.java
@@ -97,7 +97,9 @@ public class DdmHandleHello extends ChunkHandler {
}
/*
- * Handle introductory packet.
+ * Handle introductory packet. This is called during JNI_CreateJavaVM
+ * before frameworks native methods are registered, so be careful not
+ * to call any APIs that depend on frameworks native code.
*/
private Chunk handleHELO(Chunk request) {
if (false)
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 0f7c93095300..13706541962b 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -22,7 +22,6 @@ import android.hardware.ICameraServiceListener;
import android.hardware.IProCameraUser;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
-import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -279,7 +278,7 @@ public final class CameraManager {
// TODO: this class needs unit tests
// TODO: extract class into top level
- private class CameraServiceListener extends Binder implements ICameraServiceListener {
+ private class CameraServiceListener extends ICameraServiceListener.Stub {
// Keep up-to-date with ICameraServiceListener.h
diff --git a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
index 9f569cb1edb8..4172238a3c25 100644
--- a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
+++ b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
@@ -25,6 +25,6 @@ interface ICameraDeviceCallbacks
* Keep up-to-date with frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
*/
- void notifyCallback(int msgType, int ext1, int ext2);
- void onResultReceived(int frameId, in CameraMetadata result);
+ oneway void notifyCallback(int msgType, int ext1, int ext2);
+ oneway void onResultReceived(int frameId, in CameraMetadata result);
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java
index 9587680a41d9..fa790510b46b 100644
--- a/core/java/android/hardware/camera2/impl/CameraDevice.java
+++ b/core/java/android/hardware/camera2/impl/CameraDevice.java
@@ -24,7 +24,6 @@ import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraProperties;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.utils.CameraRuntimeException;
-import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -40,6 +39,7 @@ import java.util.Stack;
public class CameraDevice implements android.hardware.camera2.CameraDevice {
private final String TAG;
+ private final boolean DEBUG;
// TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
private ICameraDeviceUser mRemoteDevice;
@@ -59,6 +59,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
public CameraDevice(String cameraId) {
mCameraId = cameraId;
TAG = String.format("CameraDevice-%s-JV", mCameraId);
+ DEBUG = Log.isLoggable(TAG, Log.DEBUG);
}
public CameraDeviceCallbacks getCallbacks() {
@@ -288,7 +289,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
// TODO: unit tests
- public class CameraDeviceCallbacks extends Binder implements ICameraDeviceCallbacks {
+ public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
@Override
public IBinder asBinder() {
@@ -298,14 +299,17 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
// TODO: consider rename to onMessageReceived
@Override
public void notifyCallback(int msgType, int ext1, int ext2) throws RemoteException {
- Log.d(TAG, "Got message " + msgType + " ext1: " + ext1 + " , ext2: " + ext2);
+ if (DEBUG) {
+ Log.d(TAG, "Got message " + msgType + " ext1: " + ext1 + " , ext2: " + ext2);
+ }
// TODO implement rest
}
@Override
- public void onResultReceived(int frameId, CameraMetadata result) throws RemoteException {
- Log.d(TAG, "Received result for frameId " + frameId);
-
+ public void onResultReceived(int requestId, CameraMetadata result) throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "Received result for id " + requestId);
+ }
CaptureListenerHolder holder;
synchronized (mLock) {
@@ -313,7 +317,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
// exposing the methods necessary like subscribeToRequest, unsubscribe..
// TODO: make class static class
- holder = CameraDevice.this.mCaptureListenerMap.get(frameId);
+ holder = CameraDevice.this.mCaptureListenerMap.get(requestId);
// Clean up listener once we no longer expect to see it.
@@ -321,7 +325,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
// we probably want cancelRequest to return # of times it already enqueued and
// keep a counter.
if (holder != null && !holder.isRepeating()) {
- CameraDevice.this.mCaptureListenerMap.remove(frameId);
+ CameraDevice.this.mCaptureListenerMap.remove(requestId);
}
}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 75f8b5948b81..6ab810c68d51 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -128,6 +128,9 @@ public class LinkProperties implements Parcelable {
return interfaceNames;
}
+ /**
+ * Returns all the addresses on this link.
+ */
public Collection<InetAddress> getAddresses() {
Collection<InetAddress> addresses = new ArrayList<InetAddress>();
for (LinkAddress linkAddress : mLinkAddresses) {
@@ -136,14 +139,43 @@ public class LinkProperties implements Parcelable {
return Collections.unmodifiableCollection(addresses);
}
+ /**
+ * Returns all the addresses on this link and all the links stacked above it.
+ */
+ public Collection<InetAddress> getAllAddresses() {
+ Collection<InetAddress> addresses = new ArrayList<InetAddress>();
+ for (LinkAddress linkAddress : mLinkAddresses) {
+ addresses.add(linkAddress.getAddress());
+ }
+ for (LinkProperties stacked: mStackedLinks.values()) {
+ addresses.addAll(stacked.getAllAddresses());
+ }
+ return addresses;
+ }
+
public void addLinkAddress(LinkAddress address) {
if (address != null) mLinkAddresses.add(address);
}
+ /**
+ * Returns all the addresses on this link.
+ */
public Collection<LinkAddress> getLinkAddresses() {
return Collections.unmodifiableCollection(mLinkAddresses);
}
+ /**
+ * Returns all the addresses on this link and all the links stacked above it.
+ */
+ public Collection<LinkAddress> getAllLinkAddresses() {
+ Collection<LinkAddress> addresses = new ArrayList<LinkAddress>();
+ addresses.addAll(mLinkAddresses);
+ for (LinkProperties stacked: mStackedLinks.values()) {
+ addresses.addAll(stacked.getAllLinkAddresses());
+ }
+ return addresses;
+ }
+
public void addDns(InetAddress dns) {
if (dns != null) mDnses.add(dns);
}
@@ -426,13 +458,11 @@ public class LinkProperties implements Parcelable {
}
/**
- * Return two lists, a list of addresses that would be removed from
- * mLinkAddresses and a list of addresses that would be added to
- * mLinkAddress which would then result in target and mLinkAddresses
- * being the same list.
+ * Compares the addresses in this LinkProperties with another
+ * LinkProperties, examining only addresses on the base link.
*
- * @param target is a LinkProperties with the new list of addresses
- * @return the removed and added lists.
+ * @param target a LinkProperties with the new list of addresses
+ * @return the differences between the addresses.
*/
public CompareResult<LinkAddress> compareAddresses(LinkProperties target) {
/*
@@ -456,13 +486,11 @@ public class LinkProperties implements Parcelable {
}
/**
- * Return two lists, a list of dns addresses that would be removed from
- * mDnses and a list of addresses that would be added to
- * mDnses which would then result in target and mDnses
- * being the same list.
+ * Compares the DNS addresses in this LinkProperties with another
+ * LinkProperties, examining only DNS addresses on the base link.
*
- * @param target is a LinkProperties with the new list of dns addresses
- * @return the removed and added lists.
+ * @param target a LinkProperties with the new list of dns addresses
+ * @return the differences between the DNS addresses.
*/
public CompareResult<InetAddress> compareDnses(LinkProperties target) {
/*
@@ -487,15 +515,13 @@ public class LinkProperties implements Parcelable {
}
/**
- * Return two lists, a list of routes that would be removed from
- * mRoutes and a list of routes that would be added to
- * mRoutes which would then result in target and mRoutes
- * being the same list.
+ * Compares all routes in this LinkProperties with another LinkProperties,
+ * examining both the the base link and all stacked links.
*
- * @param target is a LinkProperties with the new list of routes
- * @return the removed and added lists.
+ * @param target a LinkProperties with the new list of routes
+ * @return the differences between the routes.
*/
- public CompareResult<RouteInfo> compareRoutes(LinkProperties target) {
+ public CompareResult<RouteInfo> compareAllRoutes(LinkProperties target) {
/*
* Duplicate the RouteInfos into removed, we will be removing
* routes which are common between mRoutes and target
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index e8c6dafb104f..54273eef3a58 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -52,7 +52,7 @@ public class MobileDataStateTracker implements NetworkStateTracker {
private static final String TAG = "MobileDataStateTracker";
private static final boolean DBG = true;
- private static final boolean VDBG = true;
+ private static final boolean VDBG = false;
private PhoneConstants.DataState mMobileDataState;
private ITelephony mPhoneService;
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 1d482dc07fc3..ab0543d0b78e 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -27,6 +27,8 @@ import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
+import libcore.io.Libcore;
+
/*package*/ class ZygoteStartFailedEx extends Exception {
/**
* Something prevented the zygote process startup from happening normally
@@ -647,19 +649,25 @@ public class Process {
* Returns the identifier of this process, which can be used with
* {@link #killProcess} and {@link #sendSignal}.
*/
- public static final native int myPid();
+ public static final int myPid() {
+ return Libcore.os.getpid();
+ }
/**
* Returns the identifier of this process' parent.
* @hide
*/
- public static native int myPpid();
+ public static final int myPpid() {
+ return Libcore.os.getppid();
+ }
/**
* Returns the identifier of the calling thread, which be used with
* {@link #setThreadPriority(int, int)}.
*/
- public static final native int myTid();
+ public static final int myTid() {
+ return Libcore.os.gettid();
+ }
/**
* Returns the identifier of this process's uid. This is the kernel uid
@@ -667,7 +675,9 @@ public class Process {
* app-specific sandbox. It is different from {@link #myUserHandle} in that
* a uid identifies a specific app sandbox in a specific user.
*/
- public static final native int myUid();
+ public static final int myUid() {
+ return Libcore.os.getuid();
+ }
/**
* Returns this process's user handle. This is the
diff --git a/core/java/android/print/FileDocumentAdapter.java b/core/java/android/print/FileDocumentAdapter.java
index c7011f427a60..d642a61fa6a2 100644
--- a/core/java/android/print/FileDocumentAdapter.java
+++ b/core/java/android/print/FileDocumentAdapter.java
@@ -34,8 +34,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
/**
* Adapter for printing files.
@@ -69,7 +67,7 @@ final class FileDocumentAdapter extends PrintDocumentAdapter {
}
@Override
- public void onWrite(List<PageRange> pages, FileDescriptor destination,
+ public void onWrite(PageRange[] pages, FileDescriptor destination,
CancellationSignal cancellationSignal, WriteResultCallback callback) {
mWriteFileAsyncTask = new WriteFileAsyncTask(destination, cancellationSignal, callback);
mWriteFileAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
@@ -127,9 +125,7 @@ final class FileDocumentAdapter extends PrintDocumentAdapter {
@Override
protected void onPostExecute(Void result) {
- List<PageRange> pages = new ArrayList<PageRange>();
- pages.add(PageRange.ALL_PAGES);
- mResultCallback.onWriteFinished(pages);
+ mResultCallback.onWriteFinished(new PageRange[] {PageRange.ALL_PAGES});
}
@Override
diff --git a/core/java/android/print/ILayoutResultCallback.aidl b/core/java/android/print/ILayoutResultCallback.aidl
index e4d79f3057ba..43b8c3094517 100644
--- a/core/java/android/print/ILayoutResultCallback.aidl
+++ b/core/java/android/print/ILayoutResultCallback.aidl
@@ -16,7 +16,6 @@
package android.print;
-import android.os.ICancellationSignal;
import android.print.PrintDocumentInfo;
/**
@@ -25,7 +24,6 @@ import android.print.PrintDocumentInfo;
* @hide
*/
oneway interface ILayoutResultCallback {
- void onLayoutStarted(ICancellationSignal cancellationSignal);
- void onLayoutFinished(in PrintDocumentInfo info, boolean changed);
- void onLayoutFailed(CharSequence error);
+ void onLayoutFinished(in PrintDocumentInfo info, boolean changed, int sequence);
+ void onLayoutFailed(CharSequence error, int sequence);
}
diff --git a/core/java/android/print/IPrintDocumentAdapter.aidl b/core/java/android/print/IPrintDocumentAdapter.aidl
index 04da15721c83..b12c922cca6e 100644
--- a/core/java/android/print/IPrintDocumentAdapter.aidl
+++ b/core/java/android/print/IPrintDocumentAdapter.aidl
@@ -31,8 +31,8 @@ import android.print.PrintAttributes;
oneway interface IPrintDocumentAdapter {
void start();
void layout(in PrintAttributes oldAttributes, in PrintAttributes newAttributes,
- ILayoutResultCallback callback, in Bundle metadata);
- void write(in List<PageRange> pages, in ParcelFileDescriptor fd,
- IWriteResultCallback callback);
+ ILayoutResultCallback callback, in Bundle metadata, int sequence);
+ void write(in PageRange[] pages, in ParcelFileDescriptor fd,
+ IWriteResultCallback callback, int sequence);
void finish();
}
diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl
index a466e741bc90..37ae2ca8b033 100644
--- a/core/java/android/print/IPrintManager.aidl
+++ b/core/java/android/print/IPrintManager.aidl
@@ -33,5 +33,4 @@ interface IPrintManager {
in IPrintDocumentAdapter printAdapter, in PrintAttributes attributes,
int appId, int userId);
void cancelPrintJob(int printJobId, int appId, int userId);
-
}
diff --git a/core/java/android/print/IPrintSpoolerCallbacks.aidl b/core/java/android/print/IPrintSpoolerCallbacks.aidl
index 7912964efecc..51b5439e6401 100644
--- a/core/java/android/print/IPrintSpoolerCallbacks.aidl
+++ b/core/java/android/print/IPrintSpoolerCallbacks.aidl
@@ -28,9 +28,9 @@ import java.util.List;
*/
oneway interface IPrintSpoolerCallbacks {
void onGetPrintJobInfosResult(in List<PrintJobInfo> printJob, int sequence);
- void onGetPrintJobInfoResult(in PrintJobInfo printJob, int sequence);
void onCreatePrintJobResult(in PrintJobInfo printJob, int sequence);
void onCancelPrintJobResult(boolean canceled, int sequence);
void onSetPrintJobStateResult(boolean success, int sequence);
void onSetPrintJobTagResult(boolean success, int sequence);
+ void onGetPrintJobInfoResult(in PrintJobInfo printJob, int sequence);
}
diff --git a/core/java/android/print/IPrintSpoolerClient.aidl b/core/java/android/print/IPrintSpoolerClient.aidl
index 47975e1eafa9..46857e4eac98 100644
--- a/core/java/android/print/IPrintSpoolerClient.aidl
+++ b/core/java/android/print/IPrintSpoolerClient.aidl
@@ -18,6 +18,7 @@ package android.print;
import android.content.ComponentName;
import android.print.IPrinterDiscoveryObserver;
+import android.print.PrinterId;
import android.print.PrintJobInfo;
@@ -30,6 +31,7 @@ oneway interface IPrintSpoolerClient {
void onPrintJobQueued(in PrintJobInfo printJob);
void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer);
void onStopPrinterDiscovery();
+ void onRequestUpdatePrinters(in List<PrinterId> printerIds);
void onAllPrintJobsForServiceHandled(in ComponentName printService);
void onAllPrintJobsHandled();
}
diff --git a/core/java/android/print/IPrintSpoolerObserver.aidl b/core/java/android/print/IPrintSpoolerObserver.aidl
deleted file mode 100644
index 7b8f40e4ef10..000000000000
--- a/core/java/android/print/IPrintSpoolerObserver.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2013 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 android.print;
-
-import android.print.PrinterId;
-import android.print.PrinterInfo;
-
-/**
- * Interface for observing the state of the print spooler.
- *
- * @hide
- */
-oneway interface IPrinterDiscoveryObserver {
- void onPrintJobQueued(in PrinterId printerId, in PrintJobInfo printJob);
- void onAllPrintJobsHandled(in ComponentName printService);
- void onAllPrintJobsHandled();
-}
diff --git a/core/java/android/print/IPrinterDiscoveryObserver.aidl b/core/java/android/print/IPrinterDiscoveryObserver.aidl
index 39aeb8c73ec9..deabbcb04d77 100644
--- a/core/java/android/print/IPrinterDiscoveryObserver.aidl
+++ b/core/java/android/print/IPrinterDiscoveryObserver.aidl
@@ -25,6 +25,7 @@ import android.print.PrinterInfo;
* @hide
*/
oneway interface IPrinterDiscoveryObserver {
- void addDiscoveredPrinters(in List<PrinterInfo> printers);
- void removeDiscoveredPrinters(in List<PrinterId> printers);
+ void onPrintersAdded(in List<PrinterInfo> printers);
+ void onPrintersRemoved(in List<PrinterId> printers);
+ void onPrintersUpdated(in List<PrinterInfo> printers);
}
diff --git a/core/java/android/print/IWriteResultCallback.aidl b/core/java/android/print/IWriteResultCallback.aidl
index d5428b145aff..8281c4e01c65 100644
--- a/core/java/android/print/IWriteResultCallback.aidl
+++ b/core/java/android/print/IWriteResultCallback.aidl
@@ -16,7 +16,6 @@
package android.print;
-import android.os.ICancellationSignal;
import android.print.PageRange;
/**
@@ -25,7 +24,6 @@ import android.print.PageRange;
* @hide
*/
oneway interface IWriteResultCallback {
- void onWriteStarted(ICancellationSignal cancellationSignal);
- void onWriteFinished(in List<PageRange> pages);
- void onWriteFailed(CharSequence error);
+ void onWriteFinished(in PageRange[] pages, int sequence);
+ void onWriteFailed(CharSequence error, int sequence);
}
diff --git a/core/java/android/print/PageRange.java b/core/java/android/print/PageRange.java
index 9257a04f4f0c..ba455f622810 100644
--- a/core/java/android/print/PageRange.java
+++ b/core/java/android/print/PageRange.java
@@ -93,8 +93,38 @@ public final class PageRange implements Parcelable {
}
@Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mEnd;
+ result = prime * result + mStart;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ PageRange other = (PageRange) obj;
+ if (mEnd != other.mEnd) {
+ return false;
+ }
+ if (mStart != other.mStart) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public String toString() {
- if (this == ALL_PAGES) {
+ if (mStart == 0 && mEnd == Integer.MAX_VALUE) {
return "PageRange[<all pages>]";
}
StringBuilder builder = new StringBuilder();
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index 87d75c0dfbee..911e3803d648 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -77,7 +77,6 @@ public final class PrintAttributes implements Parcelable {
private int mColorMode;
private int mFittingMode;
private int mOrientation;
- private int mCopies;
PrintAttributes() {
/* hide constructor */
@@ -93,7 +92,6 @@ public final class PrintAttributes implements Parcelable {
mColorMode = parcel.readInt();
mFittingMode = parcel.readInt();
mOrientation = parcel.readInt();
- mCopies = parcel.readInt();
}
/**
@@ -302,29 +300,6 @@ public final class PrintAttributes implements Parcelable {
mOrientation = orientation;
}
- /**
- * Gets the number of copies.
- *
- * @return The number of copies or zero if not set.
- */
- public int getCopies() {
- return mCopies;
- }
-
- /**
- * Sets the number of copies.
- *
- * @param copyCount The number of copies.
- *
- * @hide
- */
- public void setCopies(int copyCount) {
- if (copyCount < 1) {
- throw new IllegalArgumentException("Copies must be more than one.");
- }
- mCopies = copyCount;
- }
-
@Override
public void writeToParcel(Parcel parcel, int flags) {
if (mMediaSize != null) {
@@ -361,7 +336,6 @@ public final class PrintAttributes implements Parcelable {
parcel.writeInt(mColorMode);
parcel.writeInt(mFittingMode);
parcel.writeInt(mOrientation);
- parcel.writeInt(mCopies);
}
@Override
@@ -369,6 +343,101 @@ public final class PrintAttributes implements Parcelable {
return 0;
}
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mColorMode;
+ result = prime * result + mDuplexMode;
+ result = prime * result + mFittingMode;
+ result = prime * result + mOrientation;
+ result = prime * result + ((mInputTray == null) ? 0 : mInputTray.hashCode());
+ result = prime * result + ((mMargins == null) ? 0 : mMargins.hashCode());
+ result = prime * result + ((mMediaSize == null) ? 0 : mMediaSize.hashCode());
+ result = prime * result + ((mOutputTray == null) ? 0 : mOutputTray.hashCode());
+ result = prime * result + ((mResolution == null) ? 0 : mResolution.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ PrintAttributes other = (PrintAttributes) obj;
+ if (mColorMode != other.mColorMode) {
+ return false;
+ }
+ if (mDuplexMode != other.mDuplexMode) {
+ return false;
+ }
+ if (mFittingMode != other.mFittingMode) {
+ return false;
+ }
+ if (mOrientation != other.mOrientation) {
+ return false;
+ }
+ if (mInputTray == null) {
+ if (other.mInputTray != null) {
+ return false;
+ }
+ } else if (!mInputTray.equals(other.mInputTray)) {
+ return false;
+ }
+ if (mOutputTray == null) {
+ if (other.mOutputTray != null) {
+ return false;
+ }
+ } else if (!mOutputTray.equals(other.mOutputTray)) {
+ return false;
+ }
+ if (mMargins == null) {
+ if (other.mMargins != null) {
+ return false;
+ }
+ } else if (!mMargins.equals(other.mMargins)) {
+ return false;
+ }
+ if (mMediaSize == null) {
+ if (other.mMediaSize != null) {
+ return false;
+ }
+ } else if (!mMediaSize.equals(other.mMediaSize)) {
+ return false;
+ }
+ if (mResolution == null) {
+ if (other.mResolution != null) {
+ return false;
+ }
+ } else if (!mResolution.equals(other.mResolution)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("PrintAttributes{");
+ builder.append("mediaSize: ").append(mMediaSize);
+ builder.append(", resolution: ").append(mResolution);
+ builder.append(", margins: ").append(mMargins);
+ builder.append(", inputTray: ").append(mInputTray);
+ builder.append(", outputTray: ").append(mOutputTray);
+ builder.append(", colorMode: ").append(colorModeToString(mColorMode));
+ builder.append(", duplexMode: ").append(duplexModeToString(mDuplexMode));
+ builder.append(", fittingMode: ").append(fittingModeToString(mFittingMode));
+ builder.append(", orientation: ").append(orientationToString(mOrientation));
+ builder.append("}");
+ return builder.toString();
+ }
+
/** hide */
public void clear() {
mMediaSize = null;
@@ -380,7 +449,6 @@ public final class PrintAttributes implements Parcelable {
mColorMode = 0;
mFittingMode = 0;
mOrientation = 0;
- mCopies = 0;
}
/**
@@ -396,7 +464,6 @@ public final class PrintAttributes implements Parcelable {
mColorMode = other.mColorMode;
mFittingMode = other.mFittingMode;
mOrientation = other.mOrientation;
- mCopies = other.mCopies;
}
/**
@@ -954,6 +1021,44 @@ public final class PrintAttributes implements Parcelable {
}
@Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mId == null) ? 0 : mId.hashCode());
+ result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode());
+ result = prime * result + mWidthMils;
+ result = prime * result + mHeightMils;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ MediaSize other = (MediaSize) obj;
+ if (!TextUtils.equals(mId, other.mId)) {
+ return false;
+ }
+ if (!TextUtils.equals(mLabel, other.mLabel)) {
+ return false;
+ }
+ if (mWidthMils != other.mWidthMils) {
+ return false;
+ }
+ if (mHeightMils != other.mHeightMils) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("MediaSize{");
@@ -1061,6 +1166,44 @@ public final class PrintAttributes implements Parcelable {
}
@Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mId == null) ? 0 : mId.hashCode());
+ result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode());
+ result = prime * result + mHorizontalDpi;
+ result = prime * result + mVerticalDpi;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Resolution other = (Resolution) obj;
+ if (!TextUtils.equals(mId, other.mId)) {
+ return false;
+ }
+ if (!TextUtils.equals(mLabel, other.mLabel)) {
+ return false;
+ }
+ if (mHorizontalDpi != other.mHorizontalDpi) {
+ return false;
+ }
+ if (mVerticalDpi != other.mVerticalDpi) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Resolution{");
@@ -1166,6 +1309,44 @@ public final class PrintAttributes implements Parcelable {
}
@Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mBottomMils;
+ result = prime * result + mLeftMils;
+ result = prime * result + mRightMils;
+ result = prime * result + mTopMils;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Margins other = (Margins) obj;
+ if (mBottomMils != other.mBottomMils) {
+ return false;
+ }
+ if (mLeftMils != other.mLeftMils) {
+ return false;
+ }
+ if (mRightMils != other.mRightMils) {
+ return false;
+ }
+ if (mTopMils != other.mTopMils) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Margins{");
@@ -1235,6 +1416,36 @@ public final class PrintAttributes implements Parcelable {
}
@Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mId == null) ? 0 : mId.hashCode());
+ result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Tray other = (Tray) obj;
+ if (!TextUtils.equals(mId, other.mId)) {
+ return false;
+ }
+ if (!TextUtils.equals(mLabel, other.mLabel)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Tray{");
@@ -1246,21 +1457,6 @@ public final class PrintAttributes implements Parcelable {
}
}
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("PrintAttributes{");
- builder.append("mediaSize: ").append(mMediaSize);
- builder.append(", resolution: ").append(mResolution);
- builder.append(", margins: ").append(mMargins);
- builder.append(", duplexMode: ").append(duplexModeToString(mDuplexMode));
- builder.append(", colorMode: ").append(colorModeToString(mColorMode));
- builder.append(", fittingMode: ").append(fittingModeToString(mFittingMode));
- builder.append(", orientation: ").append(orientationToString(mOrientation));
- builder.append(", copies: ").append(mCopies);
- return builder.toString();
- }
-
private static String duplexModeToString(int duplexMode) {
switch (duplexMode) {
case DUPLEX_MODE_NONE: {
@@ -1412,7 +1608,7 @@ public final class PrintAttributes implements Parcelable {
* @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
*/
public Builder setDuplexMode(int duplexMode) {
- if (Integer.bitCount(duplexMode) != 1) {
+ if (Integer.bitCount(duplexMode) > 1) {
throw new IllegalArgumentException("can specify at most one duplexMode bit.");
}
mAttributes.setDuplexMode(duplexMode);
@@ -1471,17 +1667,6 @@ public final class PrintAttributes implements Parcelable {
}
/**
- * Sets the number of copies.
- *
- * @param copyCount A greater or equal to zero copy count.
- * @return This builder.
- */
- public Builder setCopyCount(int copyCount) {
- mAttributes.setCopies(copyCount);
- return this;
- }
-
- /**
* Creates a new {@link PrintAttributes} instance.
*
* @return The new instance.
diff --git a/core/java/android/print/PrintDocumentAdapter.java b/core/java/android/print/PrintDocumentAdapter.java
index 1f83a451a861..d3202262bbd5 100644
--- a/core/java/android/print/PrintDocumentAdapter.java
+++ b/core/java/android/print/PrintDocumentAdapter.java
@@ -41,7 +41,7 @@ import java.util.List;
* <li>
* After every call to {@link #onLayout(PrintAttributes, PrintAttributes,
* CancellationSignal, LayoutResultCallback, Bundle)}, you may get a call to
- * {@link #onWrite(List, FileDescriptor, CancellationSignal, WriteResultCallback)}
+ * {@link #onWrite(PageRange[], FileDescriptor, CancellationSignal, WriteResultCallback)}
* asking you to write a PDF file with the content for specific pages.
* </li>
* <li>
@@ -64,7 +64,7 @@ import java.util.List;
* PrintAttributes, CancellationSignal, LayoutResultCallback, Bundle)} on
* the UI thread (assuming onStart initializes resources needed for layout).
* This will ensure that the UI does not change while you are laying out the
- * printed content. Then you can handle {@link #onWrite(List, FileDescriptor,
+ * printed content. Then you can handle {@link #onWrite(PageRange[], FileDescriptor,
* CancellationSignal, WriteResultCallback)} and {@link #onFinish()} on another
* thread. This will ensure that the UI is frozen for the minimal amount of
* time. Also this assumes that you will generate the printed content in
@@ -141,7 +141,7 @@ public abstract class PrintDocumentAdapter {
* made on the main thread.
* </p>
*
- * @param pages The pages whose content to print.
+ * @param pages The pages whose content to print - non-overlapping in ascending order.
* @param destination The destination file descriptor to which to write.
* @param cancellationSignal Signal for observing cancel writing requests.
* @param callback Callback to inform the system for the write result.
@@ -149,7 +149,7 @@ public abstract class PrintDocumentAdapter {
* @see WriteResultCallback
* @see CancellationSignal
*/
- public abstract void onWrite(List<PageRange> pages, FileDescriptor destination,
+ public abstract void onWrite(PageRange[] pages, FileDescriptor destination,
CancellationSignal cancellationSignal, WriteResultCallback callback);
/**
@@ -163,7 +163,7 @@ public abstract class PrintDocumentAdapter {
/**
* Base class for implementing a callback for the result of {@link
- * PrintDocumentAdapter#onWrite(List, FileDescriptor, CancellationSignal,
+ * PrintDocumentAdapter#onWrite(PageRange[], FileDescriptor, CancellationSignal,
* WriteResultCallback)}.
*/
public static abstract class WriteResultCallback {
@@ -178,9 +178,9 @@ public abstract class PrintDocumentAdapter {
/**
* Notifies that all the data was written.
*
- * @param pages The pages that were written.
+ * @param pages The pages that were written. Cannot be null or empty.
*/
- public void onWriteFinished(List<PageRange> pages) {
+ public void onWriteFinished(PageRange[] pages) {
/* do nothing - stub */
}
@@ -192,6 +192,13 @@ public abstract class PrintDocumentAdapter {
public void onWriteFailed(CharSequence error) {
/* do nothing - stub */
}
+
+ /**
+ * Notifies that write was cancelled as a result of a cancellation request.
+ */
+ public void onWriteCancelled() {
+ /* do nothing - stub */
+ }
}
/**
@@ -211,7 +218,7 @@ public abstract class PrintDocumentAdapter {
/**
* Notifies that the layout finished and whether the content changed.
*
- * @param info An info object describing the document.
+ * @param info An info object describing the document. Cannot be null.
* @param changed Whether the layout changed.
*
* @see PrintDocumentInfo
@@ -228,5 +235,12 @@ public abstract class PrintDocumentAdapter {
public void onLayoutFailed(CharSequence error) {
/* do nothing - stub */
}
+
+ /**
+ * Notifies that layout was cancelled as a result of a cancellation request.
+ */
+ public void onLayoutCancelled() {
+ /* do nothing - stub */
+ }
}
}
diff --git a/core/java/android/print/PrintDocumentInfo.java b/core/java/android/print/PrintDocumentInfo.java
index 7d42b3a2e4e9..29e8e7ce9566 100644
--- a/core/java/android/print/PrintDocumentInfo.java
+++ b/core/java/android/print/PrintDocumentInfo.java
@@ -111,6 +111,36 @@ public final class PrintDocumentInfo implements Parcelable {
}
@Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mContentType;
+ result = prime * result + mPageCount;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ PrintDocumentInfo other = (PrintDocumentInfo) obj;
+ if (mContentType != other.mContentType) {
+ return false;
+ }
+ if (mPageCount != other.mPageCount) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("PrintDocumentInfo{");
diff --git a/core/java/android/print/PrintJob.java b/core/java/android/print/PrintJob.java
index a5e0b796802a..de28bd3c8225 100644
--- a/core/java/android/print/PrintJob.java
+++ b/core/java/android/print/PrintJob.java
@@ -55,6 +55,9 @@ public final class PrintJob {
* @return The print job info.
*/
public PrintJobInfo getInfo() {
+ if (isInImmutableState()) {
+ return mCachedInfo;
+ }
PrintJobInfo info = mPrintManager.getPrintJobInfo(mId);
if (info != null) {
mCachedInfo = info;
@@ -66,7 +69,15 @@ public final class PrintJob {
* Cancels this print job.
*/
public void cancel() {
- mPrintManager.cancelPrintJob(mId);
+ if (!isInImmutableState()) {
+ mPrintManager.cancelPrintJob(mId);
+ }
+ }
+
+ private boolean isInImmutableState() {
+ final int state = mCachedInfo.getState();
+ return state == PrintJobInfo.STATE_COMPLETED
+ || state == PrintJobInfo.STATE_CANCELED;
}
@Override
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 97384d98cf1f..39546f314247 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -19,6 +19,8 @@ package android.print;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Arrays;
+
/**
* This class represents the description of a print job.
*/
@@ -119,6 +121,9 @@ public final class PrintJobInfo implements Parcelable {
/** Optional tag assigned by a print service.*/
private String mTag;
+ /** How many copies to print. */
+ private int mCopies;
+
/** The pages to print */
private PageRange[] mPageRanges;
@@ -142,6 +147,8 @@ public final class PrintJobInfo implements Parcelable {
mAppId = other.mAppId;
mUserId = other.mUserId;
mTag = other.mTag;
+ mCopies = other.mCopies;
+ mPageRanges = other.mPageRanges;
mAttributes = other.mAttributes;
mDocumentInfo = other.mDocumentInfo;
}
@@ -154,8 +161,13 @@ public final class PrintJobInfo implements Parcelable {
mAppId = parcel.readInt();
mUserId = parcel.readInt();
mTag = parcel.readString();
+ mCopies = parcel.readInt();
if (parcel.readInt() == 1) {
- mPageRanges = (PageRange[]) parcel.readParcelableArray(null);
+ Parcelable[] parcelables = parcel.readParcelableArray(null);
+ mPageRanges = new PageRange[parcelables.length];
+ for (int i = 0; i < parcelables.length; i++) {
+ mPageRanges[i] = (PageRange) parcelables[i];
+ }
}
if (parcel.readInt() == 1) {
mAttributes = PrintAttributes.CREATOR.createFromParcel(parcel);
@@ -310,6 +322,29 @@ public final class PrintJobInfo implements Parcelable {
}
/**
+ * Gets the number of copies.
+ *
+ * @return The number of copies or zero if not set.
+ */
+ public int getCopies() {
+ return mCopies;
+ }
+
+ /**
+ * Sets the number of copies.
+ *
+ * @param copyCount The number of copies.
+ *
+ * @hide
+ */
+ public void setCopies(int copyCount) {
+ if (copyCount < 1) {
+ throw new IllegalArgumentException("Copies must be more than one.");
+ }
+ mCopies = copyCount;
+ }
+
+ /**
* Gets the included pages.
*
* @return The included pages or <code>null</code> if not set.
@@ -385,6 +420,7 @@ public final class PrintJobInfo implements Parcelable {
parcel.writeInt(mAppId);
parcel.writeInt(mUserId);
parcel.writeString(mTag);
+ parcel.writeInt(mCopies);
if (mPageRanges != null) {
parcel.writeInt(1);
parcel.writeParcelableArray(mPageRanges, flags);
@@ -413,10 +449,14 @@ public final class PrintJobInfo implements Parcelable {
builder.append(", id: ").append(mId);
builder.append(", status: ").append(stateToString(mState));
builder.append(", printer: " + mPrinterId);
+ builder.append(", tag: ").append(mTag);
+ builder.append(", copies: ").append(mCopies);
builder.append(", attributes: " + (mAttributes != null
? mAttributes.toString() : null));
builder.append(", documentInfo: " + (mDocumentInfo != null
? mDocumentInfo.toString() : null));
+ builder.append(", pages: " + (mPageRanges != null
+ ? Arrays.toString(mPageRanges) : null));
builder.append("}");
return builder.toString();
}
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index f9f53f60c433..9e8cfad8a73d 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -22,7 +22,6 @@ import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
-import android.os.ICancellationSignal;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
@@ -223,6 +222,11 @@ public final class PrintManager {
}
private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub {
+
+ private final Object mLock = new Object();
+
+ private CancellationSignal mLayoutOrWriteCancellation;
+
private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish()
private Handler mHandler; // Strong reference OK - cleared in finish()
@@ -239,22 +243,36 @@ public final class PrintManager {
@Override
public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
- ILayoutResultCallback callback, Bundle metadata) {
+ ILayoutResultCallback callback, Bundle metadata, int sequence) {
+ synchronized (mLock) {
+ if (mLayoutOrWriteCancellation != null) {
+ mLayoutOrWriteCancellation.cancel();
+ }
+ }
SomeArgs args = SomeArgs.obtain();
args.arg1 = oldAttributes;
args.arg2 = newAttributes;
args.arg3 = callback;
args.arg4 = metadata;
+ args.argi1 = sequence;
+ mHandler.removeMessages(MyHandler.MSG_LAYOUT);
mHandler.obtainMessage(MyHandler.MSG_LAYOUT, args).sendToTarget();
}
@Override
- public void write(List<PageRange> pages, ParcelFileDescriptor fd,
- IWriteResultCallback callback) {
+ public void write(PageRange[] pages, ParcelFileDescriptor fd,
+ IWriteResultCallback callback, int sequence) {
+ synchronized (mLock) {
+ if (mLayoutOrWriteCancellation != null) {
+ mLayoutOrWriteCancellation.cancel();
+ }
+ }
SomeArgs args = SomeArgs.obtain();
args.arg1 = pages;
args.arg2 = fd.getFileDescriptor();
args.arg3 = callback;
+ args.argi1 = sequence;
+ mHandler.removeMessages(MyHandler.MSG_WRITE);
mHandler.obtainMessage(MyHandler.MSG_WRITE, args).sendToTarget();
}
@@ -283,7 +301,6 @@ public final class PrintManager {
}
@Override
- @SuppressWarnings("unchecked")
public void handleMessage(Message message) {
if (isFinished()) {
return;
@@ -295,42 +312,116 @@ public final class PrintManager {
case MSG_LAYOUT: {
SomeArgs args = (SomeArgs) message.obj;
- PrintAttributes oldAttributes = (PrintAttributes) args.arg1;
- PrintAttributes newAttributes = (PrintAttributes) args.arg2;
- ILayoutResultCallback callback = (ILayoutResultCallback) args.arg3;
- Bundle metadata = (Bundle) args.arg4;
+ final PrintAttributes oldAttributes = (PrintAttributes) args.arg1;
+ final PrintAttributes newAttributes = (PrintAttributes) args.arg2;
+ final ILayoutResultCallback callback = (ILayoutResultCallback) args.arg3;
+ final Bundle metadata = (Bundle) args.arg4;
+ final int sequence = args.argi1;
args.recycle();
- try {
- ICancellationSignal remoteSignal = CancellationSignal.createTransport();
- callback.onLayoutStarted(remoteSignal);
-
- mDocumentAdapter.onLayout(oldAttributes, newAttributes,
- CancellationSignal.fromTransport(remoteSignal),
- new LayoutResultCallbackWrapper(callback), metadata);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error printing", re);
+ CancellationSignal cancellation = new CancellationSignal();
+ synchronized (mLock) {
+ mLayoutOrWriteCancellation = cancellation;
}
+
+ mDocumentAdapter.onLayout(oldAttributes, newAttributes,
+ cancellation, new LayoutResultCallback() {
+ @Override
+ public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
+ if (info == null) {
+ throw new IllegalArgumentException("info cannot be null");
+ }
+ synchronized (mLock) {
+ mLayoutOrWriteCancellation = null;
+ }
+ try {
+ callback.onLayoutFinished(info, changed, sequence);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
+ }
+ }
+
+ @Override
+ public void onLayoutFailed(CharSequence error) {
+ synchronized (mLock) {
+ mLayoutOrWriteCancellation = null;
+ }
+ try {
+ callback.onLayoutFailed(error, sequence);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
+ }
+ }
+
+ @Override
+ public void onLayoutCancelled() {
+ synchronized (mLock) {
+ mLayoutOrWriteCancellation = null;
+ }
+ }
+ }, metadata);
} break;
case MSG_WRITE: {
SomeArgs args = (SomeArgs) message.obj;
- List<PageRange> pages = (List<PageRange>) args.arg1;
- FileDescriptor fd = (FileDescriptor) args.arg2;
- IWriteResultCallback callback = (IWriteResultCallback) args.arg3;
+ final PageRange[] pages = (PageRange[]) args.arg1;
+ final FileDescriptor fd = (FileDescriptor) args.arg2;
+ final IWriteResultCallback callback = (IWriteResultCallback) args.arg3;
+ final int sequence = args.argi1;
args.recycle();
- try {
- ICancellationSignal remoteSignal = CancellationSignal.createTransport();
- callback.onWriteStarted(remoteSignal);
-
- mDocumentAdapter.onWrite(pages, fd,
- CancellationSignal.fromTransport(remoteSignal),
- new WriteResultCallbackWrapper(callback, fd));
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error printing", re);
- IoUtils.closeQuietly(fd);
+ CancellationSignal cancellation = new CancellationSignal();
+ synchronized (mLock) {
+ mLayoutOrWriteCancellation = cancellation;
}
+
+ mDocumentAdapter.onWrite(pages, fd, cancellation,
+ new WriteResultCallback() {
+ @Override
+ public void onWriteFinished(PageRange[] pages) {
+ if (pages == null) {
+ throw new IllegalArgumentException("pages cannot be null");
+ }
+ if (pages.length == 0) {
+ throw new IllegalArgumentException("pages cannot be empty");
+ }
+ synchronized (mLock) {
+ mLayoutOrWriteCancellation = null;
+ }
+ // Close before notifying the other end. We want
+ // to be ready by the time we announce it.
+ IoUtils.closeQuietly(fd);
+ try {
+ callback.onWriteFinished(pages, sequence);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling onWriteFinished", re);
+ }
+ }
+
+ @Override
+ public void onWriteFailed(CharSequence error) {
+ synchronized (mLock) {
+ mLayoutOrWriteCancellation = null;
+ }
+ // Close before notifying the other end. We want
+ // to be ready by the time we announce it.
+ IoUtils.closeQuietly(fd);
+ try {
+ callback.onWriteFailed(error, sequence);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling onWriteFailed", re);
+ }
+ }
+
+ @Override
+ public void onWriteCancelled() {
+ synchronized (mLock) {
+ mLayoutOrWriteCancellation = null;
+ }
+ // Just close the fd for now.
+ IoUtils.closeQuietly(fd);
+ }
+ });
} break;
case MSG_FINISH: {
@@ -346,67 +437,4 @@ public final class PrintManager {
}
}
}
-
- private static final class WriteResultCallbackWrapper extends WriteResultCallback {
-
- private final IWriteResultCallback mWrappedCallback;
- private final FileDescriptor mFd;
-
- public WriteResultCallbackWrapper(IWriteResultCallback callback,
- FileDescriptor fd) {
- mWrappedCallback = callback;
- mFd = fd;
- }
-
- @Override
- public void onWriteFinished(List<PageRange> pages) {
- try {
- // Close before notifying the other end. We want
- // to be ready by the time we announce it.
- IoUtils.closeQuietly(mFd);
- mWrappedCallback.onWriteFinished(pages);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error calling onWriteFinished", re);
- }
- }
-
- @Override
- public void onWriteFailed(CharSequence error) {
- try {
- // Close before notifying the other end. We want
- // to be ready by the time we announce it.
- IoUtils.closeQuietly(mFd);
- mWrappedCallback.onWriteFailed(error);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error calling onWriteFailed", re);
- }
- }
- }
-
- private static final class LayoutResultCallbackWrapper extends LayoutResultCallback {
-
- private final ILayoutResultCallback mWrappedCallback;
-
- public LayoutResultCallbackWrapper(ILayoutResultCallback callback) {
- mWrappedCallback = callback;
- }
-
- @Override
- public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
- try {
- mWrappedCallback.onLayoutFinished(info, changed);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
- }
- }
-
- @Override
- public void onLayoutFailed(CharSequence error) {
- try {
- mWrappedCallback.onLayoutFailed(error);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
- }
- }
- }
}
diff --git a/core/java/android/print/PrinterId.java b/core/java/android/print/PrinterId.java
index e884026aee75..e27fbb236055 100644
--- a/core/java/android/print/PrinterId.java
+++ b/core/java/android/print/PrinterId.java
@@ -19,6 +19,7 @@ package android.print;
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
/**
* This class represents the unique id of a printer.
@@ -98,7 +99,7 @@ public final class PrinterId implements Parcelable {
} else if (!mServiceComponentName.equals(other.mServiceComponentName)) {
return false;
}
- if (mLocalId != other.mLocalId) {
+ if (!TextUtils.equals(mLocalId, other.mLocalId)) {
return false;
}
return true;
diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java
index da3b6bc27a16..15bcb734b63e 100644
--- a/core/java/android/print/PrinterInfo.java
+++ b/core/java/android/print/PrinterInfo.java
@@ -23,9 +23,9 @@ import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Resolution;
import android.print.PrintAttributes.Tray;
import android.text.TextUtils;
-import android.util.SparseIntArray;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -41,8 +41,6 @@ public final class PrinterInfo implements Parcelable {
*/
public static final int DEFAULT_UNDEFINED = -1;
- private static final int MIN_COPIES = 1;
-
private static final int PROPERTY_MEDIA_SIZE = 0;
private static final int PROPERTY_RESOLUTION = 1;
private static final int PROPERTY_INPUT_TRAY = 2;
@@ -51,19 +49,22 @@ public final class PrinterInfo implements Parcelable {
private static final int PROPERTY_COLOR_MODE = 5;
private static final int PROPERTY_FITTING_MODE = 6;
private static final int PROPERTY_ORIENTATION = 7;
+ private static final int PROPERTY_COUNT = 8;
/** Printer status: the printer is ready to print. */
public static final int STATUS_READY = 1;
+ private static final Margins DEFAULT_MARGINS = new Margins(0, 0, 0, 0);
+
// TODO: Add printer status constants.
private PrinterId mId;
private CharSequence mLabel;
private int mStatus;
- private Margins mMinMargins;
- private final List<MediaSize> mMediaSizes = new ArrayList<MediaSize>(); // required
- private final List<Resolution> mResolutions = new ArrayList<Resolution>(); // required
+ private Margins mMinMargins = DEFAULT_MARGINS;
+ private List<MediaSize> mMediaSizes;
+ private List<Resolution> mResolutions;
private List<Tray> mInputTrays;
private List<Tray> mOutputTrays;
@@ -72,43 +73,83 @@ public final class PrinterInfo implements Parcelable {
private int mFittingModes;
private int mOrientations;
- private final SparseIntArray mDefaults = new SparseIntArray();
- private Margins mDefaultMargins;
+ private final int[] mDefaults = new int[PROPERTY_COUNT];
+ private Margins mDefaultMargins = DEFAULT_MARGINS;
- private PrinterInfo() {
- mDefaults.put(PROPERTY_MEDIA_SIZE, DEFAULT_UNDEFINED);
- mDefaults.put(PROPERTY_RESOLUTION, DEFAULT_UNDEFINED);
- mDefaults.put(PROPERTY_INPUT_TRAY, DEFAULT_UNDEFINED);
- mDefaults.put(PROPERTY_OUTPUT_TRAY, DEFAULT_UNDEFINED);
- mDefaults.put(PROPERTY_DUPLEX_MODE, DEFAULT_UNDEFINED);
- mDefaults.put(PROPERTY_COLOR_MODE, DEFAULT_UNDEFINED);
- mDefaults.put(PROPERTY_FITTING_MODE, DEFAULT_UNDEFINED);
- mDefaults.put(PROPERTY_ORIENTATION, DEFAULT_UNDEFINED);
+ /**
+ * @hide
+ */
+ public PrinterInfo() {
+ Arrays.fill(mDefaults, DEFAULT_UNDEFINED);
}
private PrinterInfo(PrinterInfo prototype) {
- mId = prototype.mId;
- mLabel = prototype.mLabel;
- mStatus = prototype.mStatus;
-
- mMinMargins = prototype.mMinMargins;
- mMediaSizes.addAll(prototype.mMediaSizes);
- mResolutions.addAll(prototype.mResolutions);
- mInputTrays = (prototype.mInputTrays != null)
- ? new ArrayList<Tray>(prototype.mInputTrays) : null;
- mOutputTrays = (prototype.mOutputTrays != null)
- ? new ArrayList<Tray>(prototype.mOutputTrays) : null;
-
- mDuplexModes = prototype.mDuplexModes;
- mColorModes = prototype.mColorModes;
- mFittingModes = prototype.mFittingModes;
- mOrientations = prototype.mOrientations;
-
- final int defaultCount = prototype.mDefaults.size();
+ copyFrom(prototype);
+ }
+
+ /**
+ * @hide
+ */
+ public void copyFrom(PrinterInfo other) {
+ mId = other.mId;
+ mLabel = other.mLabel;
+ mStatus = other.mStatus;
+
+ mMinMargins = other.mMinMargins;
+ if (other.mMediaSizes != null) {
+ if (mMediaSizes != null) {
+ mMediaSizes.clear();
+ mMediaSizes.addAll(other.mMediaSizes);
+ } else {
+ mMediaSizes = new ArrayList<MediaSize>(other.mMediaSizes);
+ }
+ } else {
+ mMediaSizes = null;
+ }
+
+ if (other.mResolutions != null) {
+ if (mResolutions != null) {
+ mResolutions.clear();
+ mResolutions.addAll(other.mResolutions);
+ } else {
+ mResolutions = new ArrayList<Resolution>(other.mResolutions);
+ }
+ } else {
+ mResolutions = null;
+ }
+
+ if (other.mInputTrays != null) {
+ if (mInputTrays != null) {
+ mInputTrays.clear();
+ mInputTrays.addAll(other.mInputTrays);
+ } else {
+ mInputTrays = new ArrayList<Tray>(other.mInputTrays);
+ }
+ } else {
+ mInputTrays = null;
+ }
+
+ if (other.mOutputTrays != null) {
+ if (mOutputTrays != null) {
+ mOutputTrays.clear();
+ mOutputTrays.addAll(other.mOutputTrays);
+ } else {
+ mOutputTrays = new ArrayList<Tray>(other.mOutputTrays);
+ }
+ } else {
+ mOutputTrays = null;
+ }
+
+ mDuplexModes = other.mDuplexModes;
+ mColorModes = other.mColorModes;
+ mFittingModes = other.mFittingModes;
+ mOrientations = other.mOrientations;
+
+ final int defaultCount = other.mDefaults.length;
for (int i = 0; i < defaultCount; i++) {
- mDefaults.put(prototype.mDefaults.keyAt(i), prototype.mDefaults.valueAt(i));
+ mDefaults[i] = other.mDefaults[i];
}
- mDefaultMargins = prototype.mDefaultMargins;
+ mDefaultMargins = other.mDefaultMargins;
}
/**
@@ -240,52 +281,66 @@ public final class PrinterInfo implements Parcelable {
public void getDefaults(PrintAttributes outAttributes) {
outAttributes.clear();
- // TODO: Do we want a printer to specify default copies?
- outAttributes.setCopies(MIN_COPIES);
-
outAttributes.setMargins(mDefaultMargins);
- final int mediaSizeIndex = mDefaults.get(PROPERTY_MEDIA_SIZE);
+ final int mediaSizeIndex = mDefaults[PROPERTY_MEDIA_SIZE];
if (mediaSizeIndex >= 0) {
outAttributes.setMediaSize(mMediaSizes.get(mediaSizeIndex));
}
- final int resolutionIndex = mDefaults.get(PROPERTY_RESOLUTION);
+ final int resolutionIndex = mDefaults[PROPERTY_RESOLUTION];
if (resolutionIndex >= 0) {
outAttributes.setResolution(mResolutions.get(resolutionIndex));
}
- final int inputTrayIndex = mDefaults.get(PROPERTY_INPUT_TRAY);
+ final int inputTrayIndex = mDefaults[PROPERTY_INPUT_TRAY];
if (inputTrayIndex >= 0) {
outAttributes.setInputTray(mInputTrays.get(inputTrayIndex));
}
- final int outputTrayIndex = mDefaults.get(PROPERTY_OUTPUT_TRAY);
+ final int outputTrayIndex = mDefaults[PROPERTY_OUTPUT_TRAY];
if (outputTrayIndex >= 0) {
outAttributes.setOutputTray(mOutputTrays.get(outputTrayIndex));
}
- final int duplexMode = mDefaults.get(PROPERTY_DUPLEX_MODE);
+ final int duplexMode = mDefaults[PROPERTY_DUPLEX_MODE];
if (duplexMode > 0) {
outAttributes.setDuplexMode(duplexMode);
}
- final int colorMode = mDefaults.get(PROPERTY_COLOR_MODE);
+ final int colorMode = mDefaults[PROPERTY_COLOR_MODE];
if (colorMode > 0) {
outAttributes.setColorMode(mColorModes & colorMode);
}
- final int fittingMode = mDefaults.get(PROPERTY_FITTING_MODE);
+ final int fittingMode = mDefaults[PROPERTY_FITTING_MODE];
if (fittingMode > 0) {
outAttributes.setFittingMode(fittingMode);
}
- final int orientation = mDefaults.get(PROPERTY_ORIENTATION);
+ final int orientation = mDefaults[PROPERTY_ORIENTATION];
if (orientation > 0) {
outAttributes.setOrientation(orientation);
}
}
+ /**
+ * Gets whether this printer info is fully-populated, i.e. whether
+ * all required attributes are specified. See the {@link Builder}
+ * documentation for which attributes are required.
+ *
+ * @return Whether this info has all required attributes.
+ */
+ public boolean hasAllRequiredAttributes() {
+ return (mMediaSizes != null && !mMediaSizes.isEmpty()
+ && mResolutions != null && !mResolutions.isEmpty()
+ && mColorModes != 0 || mOrientations != 0
+ && mDefaults[PROPERTY_MEDIA_SIZE] != DEFAULT_UNDEFINED
+ && mDefaults[PROPERTY_RESOLUTION] != DEFAULT_UNDEFINED
+ && mDefaults[PROPERTY_COLOR_MODE] != DEFAULT_UNDEFINED
+ && mDefaults[PROPERTY_ORIENTATION] != DEFAULT_UNDEFINED);
+ }
+
private PrinterInfo(Parcel parcel) {
mId = parcel.readParcelable(null);
mLabel = parcel.readCharSequence();
@@ -333,6 +388,112 @@ public final class PrinterInfo implements Parcelable {
}
@Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mId == null) ? 0 : mId.hashCode());
+ result = prime * result + ((mLabel == null) ? 0 : mLabel.hashCode());
+ result = prime * result + mStatus;
+ result = prime * result + ((mMinMargins == null) ? 0 : mMinMargins.hashCode());
+ result = prime * result + ((mMediaSizes == null) ? 0 : mMediaSizes.hashCode());
+ result = prime * result + ((mResolutions == null) ? 0 : mResolutions.hashCode());
+ result = prime * result + ((mInputTrays == null) ? 0 : mInputTrays.hashCode());
+ result = prime * result + ((mOutputTrays == null) ? 0 : mOutputTrays.hashCode());
+ result = prime * result + mDuplexModes;
+ result = prime * result + mColorModes;
+ result = prime * result + mFittingModes;
+ result = prime * result + mOrientations;
+ result = prime * result + Arrays.hashCode(mDefaults);
+ result = prime * result + ((mDefaultMargins == null) ? 0 : mDefaultMargins.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ PrinterInfo other = (PrinterInfo) obj;
+ if (mId == null) {
+ if (other.mId != null) {
+ return false;
+ }
+ } else if (!mId.equals(other.mId)) {
+ return false;
+ }
+ if (!TextUtils.equals(mLabel, other.mLabel)) {
+ return false;
+ }
+ if (mStatus != other.mStatus) {
+ return false;
+ }
+ if (mMinMargins == null) {
+ if (other.mMinMargins != null) {
+ return false;
+ }
+ } else if (!mMinMargins.equals(other.mMinMargins)) {
+ return false;
+ }
+ if (mMediaSizes == null) {
+ if (other.mMediaSizes != null) {
+ return false;
+ }
+ } else if (!mMediaSizes.equals(other.mMediaSizes)) {
+ return false;
+ }
+ if (mResolutions == null) {
+ if (other.mResolutions != null) {
+ return false;
+ }
+ } else if (!mResolutions.equals(other.mResolutions)) {
+ return false;
+ }
+ if (mInputTrays == null) {
+ if (other.mInputTrays != null) {
+ return false;
+ }
+ } else if (!mInputTrays.equals(other.mInputTrays)) {
+ return false;
+ }
+ if (mOutputTrays == null) {
+ if (other.mOutputTrays != null) {
+ return false;
+ }
+ } else if (!mOutputTrays.equals(other.mOutputTrays)) {
+ return false;
+ }
+ if (mDuplexModes != other.mDuplexModes) {
+ return false;
+ }
+ if (mColorModes != other.mColorModes) {
+ return false;
+ }
+ if (mFittingModes != other.mFittingModes) {
+ return false;
+ }
+ if (mOrientations != other.mOrientations) {
+ return false;
+ }
+ if (!Arrays.equals(mDefaults, other.mDefaults)) {
+ return false;
+ }
+ if (mDefaultMargins == null) {
+ if (other.mDefaultMargins != null) {
+ return false;
+ }
+ } else if (!mDefaultMargins.equals(other.mDefaultMargins)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("PrinterInfo{");
@@ -356,12 +517,19 @@ public final class PrinterInfo implements Parcelable {
private void readMediaSizes(Parcel parcel) {
final int mediaSizeCount = parcel.readInt();
+ if (mediaSizeCount > 0 && mMediaSizes == null) {
+ mMediaSizes = new ArrayList<MediaSize>();
+ }
for (int i = 0; i < mediaSizeCount; i++) {
mMediaSizes.add(MediaSize.createFromParcel(parcel));
}
}
private void writeResolutions(Parcel parcel) {
+ if (mResolutions == null) {
+ parcel.writeInt(0);
+ return;
+ }
final int resolutionCount = mResolutions.size();
parcel.writeInt(resolutionCount);
for (int i = 0; i < resolutionCount; i++) {
@@ -371,6 +539,9 @@ public final class PrinterInfo implements Parcelable {
private void readResolutions(Parcel parcel) {
final int resolutionCount = parcel.readInt();
+ if (resolutionCount > 0 && mResolutions == null) {
+ mResolutions = new ArrayList<Resolution>();
+ }
for (int i = 0; i < resolutionCount; i++) {
mResolutions.add(Resolution.createFromParcel(parcel));
}
@@ -440,15 +611,15 @@ public final class PrinterInfo implements Parcelable {
private void readDefaults(Parcel parcel) {
final int defaultCount = parcel.readInt();
for (int i = 0; i < defaultCount; i++) {
- mDefaults.append(mDefaults.size(), parcel.readInt());
+ mDefaults[i] = parcel.readInt();
}
}
private void writeDefaults(Parcel parcel) {
- final int defaultCount = mDefaults.size();
+ final int defaultCount = mDefaults.length;
parcel.writeInt(defaultCount);
for (int i = 0; i < defaultCount; i++) {
- parcel.writeInt(mDefaults.valueAt(i));
+ parcel.writeInt(mDefaults[i]);
}
}
@@ -467,11 +638,11 @@ public final class PrinterInfo implements Parcelable {
/**
* Creates a new instance.
*
- * @param printerId The printer id.
- * @param label The human readable printer label.
+ * @param printerId The printer id. Cannot be null.
+ * @param label The human readable printer label. Cannot be null or empty.
*
- * @throws IllegalArgumentException IF the printer id is null.
- * @throws IllegalArgumentException IF the label is empty.
+ * @throws IllegalArgumentException If the printer id is null.
+ * @throws IllegalArgumentException If the label is empty.
*/
public Builder(PrinterId printerId, CharSequence label) {
if (printerId == null) {
@@ -514,11 +685,14 @@ public final class PrinterInfo implements Parcelable {
* @see PrintAttributes.MediaSize
*/
public Builder addMediaSize(MediaSize mediaSize, boolean isDefault) {
+ if (mPrototype.mMediaSizes == null) {
+ mPrototype.mMediaSizes = new ArrayList<MediaSize>();
+ }
final int insertionIndex = mPrototype.mMediaSizes.size();
mPrototype.mMediaSizes.add(mediaSize);
if (isDefault) {
throwIfDefaultAlreadySpecified(PROPERTY_MEDIA_SIZE);
- mPrototype.mDefaults.put(PROPERTY_MEDIA_SIZE, insertionIndex);
+ mPrototype.mDefaults[PROPERTY_MEDIA_SIZE] = insertionIndex;
}
return this;
}
@@ -539,11 +713,14 @@ public final class PrinterInfo implements Parcelable {
* @see PrintAttributes.Resolution
*/
public Builder addResolution(Resolution resolution, boolean isDefault) {
+ if (mPrototype.mResolutions == null) {
+ mPrototype.mResolutions = new ArrayList<Resolution>();
+ }
final int insertionIndex = mPrototype.mResolutions.size();
mPrototype.mResolutions.add(resolution);
if (isDefault) {
throwIfDefaultAlreadySpecified(PROPERTY_RESOLUTION);
- mPrototype.mDefaults.put(PROPERTY_RESOLUTION, insertionIndex);
+ mPrototype.mDefaults[PROPERTY_RESOLUTION] = insertionIndex;
}
return this;
}
@@ -596,7 +773,7 @@ public final class PrinterInfo implements Parcelable {
mPrototype.mInputTrays.add(inputTray);
if (isDefault) {
throwIfDefaultAlreadySpecified(PROPERTY_INPUT_TRAY);
- mPrototype.mDefaults.put(PROPERTY_INPUT_TRAY, insertionIndex);
+ mPrototype.mDefaults[PROPERTY_INPUT_TRAY] = insertionIndex;
}
return this;
}
@@ -624,7 +801,7 @@ public final class PrinterInfo implements Parcelable {
mPrototype.mOutputTrays.add(outputTray);
if (isDefault) {
throwIfDefaultAlreadySpecified(PROPERTY_OUTPUT_TRAY);
- mPrototype.mDefaults.put(PROPERTY_OUTPUT_TRAY, insertionIndex);
+ mPrototype.mDefaults[PROPERTY_OUTPUT_TRAY] = insertionIndex;
}
return this;
}
@@ -657,7 +834,7 @@ public final class PrinterInfo implements Parcelable {
}
PrintAttributes.enforceValidColorMode(colorModes);
mPrototype.mColorModes = colorModes;
- mPrototype.mDefaults.put(PROPERTY_COLOR_MODE, defaultColorMode);
+ mPrototype.mDefaults[PROPERTY_COLOR_MODE] = defaultColorMode;
return this;
}
@@ -690,7 +867,7 @@ public final class PrinterInfo implements Parcelable {
}
PrintAttributes.enforceValidDuplexMode(defaultDuplexMode);
mPrototype.mDuplexModes = duplexModes;
- mPrototype.mDefaults.put(PROPERTY_DUPLEX_MODE, defaultDuplexMode);
+ mPrototype.mDefaults[PROPERTY_DUPLEX_MODE] = defaultDuplexMode;
return this;
}
@@ -722,7 +899,7 @@ public final class PrinterInfo implements Parcelable {
}
PrintAttributes.enfoceValidFittingMode(defaultFittingMode);
mPrototype.mFittingModes = fittingModes;
- mPrototype.mDefaults.put(PROPERTY_FITTING_MODE, defaultFittingMode);
+ mPrototype.mDefaults[PROPERTY_FITTING_MODE] = defaultFittingMode;
return this;
}
@@ -754,55 +931,21 @@ public final class PrinterInfo implements Parcelable {
}
PrintAttributes.enforceValidOrientation(defaultOrientation);
mPrototype.mOrientations = orientations;
- mPrototype.mDefaults.put(PROPERTY_ORIENTATION, defaultOrientation);
+ mPrototype.mDefaults[PROPERTY_ORIENTATION] = defaultOrientation;
return this;
}
/**
- * Crates a new {@link PrinterInfo} enforcing that all required properties
- * have need specified. See individual methods in this class for reference
- * about required attributes.
+ * Crates a new {@link PrinterInfo}.
*
* @return A new {@link PrinterInfo}.
- *
- * @throws IllegalStateException If a required attribute was not specified.
*/
public PrinterInfo create() {
- if (mPrototype.mMediaSizes == null || mPrototype.mMediaSizes.isEmpty()) {
- throw new IllegalStateException("No media size specified.");
- }
- if (mPrototype.mDefaults.valueAt(PROPERTY_MEDIA_SIZE) == DEFAULT_UNDEFINED) {
- throw new IllegalStateException("No default media size specified.");
- }
- if (mPrototype.mResolutions == null || mPrototype.mResolutions.isEmpty()) {
- throw new IllegalStateException("No resolution specified.");
- }
- if (mPrototype.mDefaults.valueAt(PROPERTY_RESOLUTION) == DEFAULT_UNDEFINED) {
- throw new IllegalStateException("No default resolution specified.");
- }
- if (mPrototype.mColorModes == 0) {
- throw new IllegalStateException("No color mode specified.");
- }
- if (mPrototype.mDefaults.valueAt(PROPERTY_COLOR_MODE) == DEFAULT_UNDEFINED) {
- throw new IllegalStateException("No default color mode specified.");
- }
- if (mPrototype.mOrientations == 0) {
- throw new IllegalStateException("No oprientation specified.");
- }
- if (mPrototype.mDefaults.valueAt(PROPERTY_ORIENTATION) == DEFAULT_UNDEFINED) {
- throw new IllegalStateException("No default orientation specified.");
- }
- if (mPrototype.mMinMargins == null) {
- mPrototype.mMinMargins = new Margins(0, 0, 0, 0);
- }
- if (mPrototype.mDefaultMargins == null) {
- mPrototype.mDefaultMargins = mPrototype.mMinMargins;
- }
return new PrinterInfo(mPrototype);
}
private void throwIfDefaultAlreadySpecified(int propertyIndex) {
- if (mPrototype.mDefaults.get(propertyIndex) != DEFAULT_UNDEFINED) {
+ if (mPrototype.mDefaults[propertyIndex] != DEFAULT_UNDEFINED) {
throw new IllegalArgumentException("Default already specified.");
}
}
diff --git a/core/java/android/printservice/IPrintService.aidl b/core/java/android/printservice/IPrintService.aidl
index c72385adda3f..e6fdbf999a35 100644
--- a/core/java/android/printservice/IPrintService.aidl
+++ b/core/java/android/printservice/IPrintService.aidl
@@ -29,8 +29,9 @@ import android.printservice.IPrintServiceClient;
*/
oneway interface IPrintService {
void setClient(IPrintServiceClient client);
- void requestCancelPrintJob(in PrintJobInfo printJobInfo);
+ void onRequestUpdatePrinters(in List<PrinterId> printerIds);
+ void onRequestCancelPrintJob(in PrintJobInfo printJobInfo);
void onPrintJobQueued(in PrintJobInfo printJobInfo);
- void startPrinterDiscovery(IPrinterDiscoveryObserver observer);
- void stopPrinterDiscovery();
+ void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer);
+ void onStopPrinterDiscovery();
}
diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java
index 80530a7cb8d6..0ac5a133b5d3 100644
--- a/core/java/android/printservice/PrintJob.java
+++ b/core/java/android/printservice/PrintJob.java
@@ -61,6 +61,9 @@ public final class PrintJob {
* @return The print job info.
*/
public PrintJobInfo getInfo() {
+ if (isInImmutableState()) {
+ return mCachedInfo;
+ }
PrintJobInfo info = null;
try {
info = mPrintServiceClient.getPrintJobInfo(mCachedInfo.getId());
@@ -182,6 +185,9 @@ public final class PrintJob {
* @return True if the tag was set, false otherwise.
*/
public boolean setTag(String tag) {
+ if (isInImmutableState()) {
+ return false;
+ }
try {
return mPrintServiceClient.setPrintJobTag(mCachedInfo.getId(), tag);
} catch (RemoteException re) {
@@ -210,6 +216,12 @@ public final class PrintJob {
return mCachedInfo.getId();
}
+ private boolean isInImmutableState() {
+ final int state = mCachedInfo.getState();
+ return state == PrintJobInfo.STATE_COMPLETED
+ || state == PrintJobInfo.STATE_CANCELED;
+ }
+
private boolean setState(int state) {
try {
if (mPrintServiceClient.setPrintJobState(mCachedInfo.getId(), state)) {
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index dde31d25678b..15e1b7399719 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -207,13 +207,14 @@ public abstract class PrintService extends Service {
* Callback requesting from this service to start printer discovery.
* At the end of the printer discovery period the system will call
* {@link #onStopPrinterDiscovery()}. Discovered printers should be
- * reported by calling #addDiscoveredPrinters(List) and reported ones
- * that disappear should be reported by calling
+ * reported by calling {@link #addDiscoveredPrinters(List)} and reported
+ * ones that disappear should be reported by calling
* {@link #removeDiscoveredPrinters(List)}.
*
* @see #onStopPrinterDiscovery()
* @see #addDiscoveredPrinters(List)
* @see #removeDiscoveredPrinters(List)
+ * @see #updateDiscoveredPrinters(List)
*/
protected abstract void onStartPrinterDiscovery();
@@ -223,6 +224,7 @@ public abstract class PrintService extends Service {
* @see #onStartPrinterDiscovery()
* @see #addDiscoveredPrinters(List)
* @see #removeDiscoveredPrinters(List)
+ * @see #updateDiscoveredPrinters(List)
*/
protected abstract void onStopPrinterDiscovery();
@@ -236,12 +238,23 @@ public abstract class PrintService extends Service {
* printers have to be added. You can call this method as many times as
* necessary during the discovery period but should not pass in already
* added printers. If a printer is already added in the same printer
- * discovery period, it will be ignored. If you want to update an already
- * added printer, you should removed it and then re-add it.
+ * discovery period, it will be ignored.
* </p>
+ * <p>
+ * A {@link PrinterInfo} can have all of its required attributes specified,
+ * or not. Whether all attributes are specified can be verified by calling
+ * {@link PrinterInfo#hasAllRequiredAttributes()}. You can add printers
+ * regardless if all required attributes are specified. When the system
+ * (and the user) needs to interact with a printer, you will receive a
+ * call to {@link #onRequestUpdatePrinters(List)}. If you fail to update
+ * a printer that was added without all required attributes via calling
+ * {@link #updateDiscoveredPrinters(List)}, then this printer will be
+ * ignored, i.e. considered unavailable.
+ * <p>
*
* @param printers A list with discovered printers.
*
+ * @see #updateDiscoveredPrinters(List)
* @see #removeDiscoveredPrinters(List)
* @see #onStartPrinterDiscovery()
* @see #onStopPrinterDiscovery()
@@ -253,7 +266,7 @@ public abstract class PrintService extends Service {
}
if (observer != null) {
try {
- observer.addDiscoveredPrinters(printers);
+ observer.onPrintersAdded(printers);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error adding discovered printers", re);
}
@@ -271,14 +284,13 @@ public abstract class PrintService extends Service {
* period by a call to {@link #addDiscoveredPrinters(List)}. You can call
* this method as many times as necessary during the discovery period
* but should not pass in already removed printer ids. If a printer with
- * a given id is already removed in the same discovery period, it will
- * be ignored. If you want to update an already added printer, you should
- * removed it and then re-add it.
+ * a given id is already removed, it will be ignored.
* </p>
*
* @param printerIds A list with disappeared printer ids.
*
* @see #addDiscoveredPrinters(List)
+ * @see #updateDiscoveredPrinters(List)
* @see #onStartPrinterDiscovery()
* @see #onStopPrinterDiscovery()
*/
@@ -289,7 +301,7 @@ public abstract class PrintService extends Service {
}
if (observer != null) {
try {
- observer.removeDiscoveredPrinters(printerIds);
+ observer.onPrintersRemoved(printerIds);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error removing discovered printers", re);
}
@@ -297,6 +309,65 @@ public abstract class PrintService extends Service {
}
/**
+ * Updates discovered printers that are already added. This method should
+ * be called during a printer discovery period, i.e. after a call to
+ * {@link #onStartPrinterDiscovery()} and before the corresponding
+ * call to {@link #onStopPrinterDiscovery()}, otherwise it does nothing.
+ * <p>
+ * For every printer discovery period all printers have to be added. You
+ * should update only printers that were added in this printer discovery
+ * period by a call to {@link #addDiscoveredPrinters(List)}. You can call
+ * this method as many times as necessary during the discovery period
+ * but should not try to update already removed or never added printers.
+ * If a printer is already removed or never added, it will be ignored.
+ * </p>
+ *
+ * @param printers A list with updated printers.
+ *
+ * @see #addDiscoveredPrinters(List)
+ * @see #removeDiscoveredPrinters(List)
+ * @see #onStartPrinterDiscovery()
+ * @see #onStopPrinterDiscovery()
+ */
+ public final void updateDiscoveredPrinters(List<PrinterInfo> printers) {
+ final IPrinterDiscoveryObserver observer;
+ synchronized (mLock) {
+ observer = mDiscoveryObserver;
+ }
+ if (observer != null) {
+ try {
+ observer.onPrintersUpdated(printers);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error updating discovered printers", re);
+ }
+ }
+ }
+
+ /**
+ * Called when the system will start interacting with a printer
+ * giving you a change to update it in case some of its capabilities
+ * have changed. For example, this method will be called when the
+ * user selects a printer. Hence, it updating this printer should
+ * be done as quickly as possible in order to achieve maximally
+ * smooth user experience.
+ * <p>
+ * A {@link PrinterInfo} can have all of its required attributes specified,
+ * or not. Whether all attributes are specified can be verified by calling
+ * {@link PrinterInfo#hasAllRequiredAttributes()}. You can add printers
+ * regardless if all required attributes are specified. When the system
+ * (and the user) needs to interact with a printer, you will receive a
+ * call to this method. If you fail to update a printer that was added
+ * without all required attributes via calling
+ * {@link #updateDiscoveredPrinters(List)}, then this printer will be
+ * ignored, i.e. considered unavailable.
+ * </p>
+ *
+ * @param printerIds The printers to be updated.
+ */
+ protected void onRequestUpdatePrinters(List<PrinterId> printerIds) {
+ }
+
+ /**
* Called when canceling of a print job is requested. The service
* should do best effort to fulfill the request. After the cancellation
* is performed, the print job should be set to a cancelled state by
@@ -373,74 +444,87 @@ public abstract class PrintService extends Service {
return new IPrintService.Stub() {
@Override
public void setClient(IPrintServiceClient client) {
- mHandler.obtainMessage(MyHandler.MESSAGE_SET_CLEINT, client).sendToTarget();
+ mHandler.obtainMessage(MyHandler.MSG_SET_CLEINT, client).sendToTarget();
}
@Override
- public void startPrinterDiscovery(IPrinterDiscoveryObserver observer) {
- mHandler.obtainMessage(MyHandler.MESSAGE_START_PRINTER_DISCOVERY,
+ public void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer) {
+ mHandler.obtainMessage(MyHandler.MSG_ON_START_PRINTER_DISCOVERY,
observer).sendToTarget();
}
@Override
- public void stopPrinterDiscovery() {
- mHandler.sendEmptyMessage(MyHandler.MESSAGE_STOP_PRINTER_DISCOVERY);
+ public void onStopPrinterDiscovery() {
+ mHandler.sendEmptyMessage(MyHandler.MSG_ON_STOP_PRINTER_DISCOVERY);
}
@Override
- public void requestCancelPrintJob(PrintJobInfo printJobInfo) {
- mHandler.obtainMessage(MyHandler.MESSAGE_CANCEL_PRINTJOB,
+ public void onRequestUpdatePrinters(List<PrinterId> printerIds) {
+ mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_UPDATE_PRINTERS,
+ printerIds).sendToTarget();
+ }
+
+ @Override
+ public void onRequestCancelPrintJob(PrintJobInfo printJobInfo) {
+ mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_CANCEL_PRINTJOB,
printJobInfo).sendToTarget();
}
@Override
public void onPrintJobQueued(PrintJobInfo printJobInfo) {
- mHandler.obtainMessage(MyHandler.MESSAGE_ON_PRINTJOB_QUEUED,
+ mHandler.obtainMessage(MyHandler.MSG_ON_PRINTJOB_QUEUED,
printJobInfo).sendToTarget();
}
};
}
private final class MyHandler extends Handler {
- public static final int MESSAGE_START_PRINTER_DISCOVERY = 1;
- public static final int MESSAGE_STOP_PRINTER_DISCOVERY = 2;
- public static final int MESSAGE_CANCEL_PRINTJOB = 3;
- public static final int MESSAGE_ON_PRINTJOB_QUEUED = 4;
- public static final int MESSAGE_SET_CLEINT = 5;
+ public static final int MSG_ON_START_PRINTER_DISCOVERY = 1;
+ public static final int MSG_ON_STOP_PRINTER_DISCOVERY = 2;
+ public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 3;
+ public static final int MSG_ON_REQUEST_UPDATE_PRINTERS = 4;
+ public static final int MSG_ON_PRINTJOB_QUEUED = 5;
+ public static final int MSG_SET_CLEINT = 6;
public MyHandler(Looper looper) {
super(looper, null, true);
}
@Override
+ @SuppressWarnings("unchecked")
public void handleMessage(Message message) {
final int action = message.what;
switch (action) {
- case MESSAGE_START_PRINTER_DISCOVERY: {
+ case MSG_ON_START_PRINTER_DISCOVERY: {
synchronized (mLock) {
mDiscoveryObserver = (IPrinterDiscoveryObserver) message.obj;
}
onStartPrinterDiscovery();
} break;
- case MESSAGE_STOP_PRINTER_DISCOVERY: {
+ case MSG_ON_STOP_PRINTER_DISCOVERY: {
synchronized (mLock) {
mDiscoveryObserver = null;
}
onStopPrinterDiscovery();
} break;
- case MESSAGE_CANCEL_PRINTJOB: {
+ case MSG_ON_REQUEST_CANCEL_PRINTJOB: {
PrintJobInfo printJobInfo = (PrintJobInfo) message.obj;
onRequestCancelPrintJob(new PrintJob(printJobInfo, mClient));
} break;
- case MESSAGE_ON_PRINTJOB_QUEUED: {
+ case MSG_ON_REQUEST_UPDATE_PRINTERS: {
+ List<PrinterId> printerIds = (List<PrinterId>) message.obj;
+ onRequestUpdatePrinters(printerIds);
+ } break;
+
+ case MSG_ON_PRINTJOB_QUEUED: {
PrintJobInfo printJobInfo = (PrintJobInfo) message.obj;
onPrintJobQueued(new PrintJob(printJobInfo, mClient));
} break;
- case MESSAGE_SET_CLEINT: {
+ case MSG_SET_CLEINT: {
IPrintServiceClient client = (IPrintServiceClient) message.obj;
synchronized (mLock) {
mClient = client;
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index c26f6d47c914..979a5a3bd648 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -16,10 +16,13 @@
package android.provider;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
+import android.content.pm.ProviderInfo;
import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
@@ -39,9 +42,10 @@ import java.io.InputStream;
public final class DocumentsContract {
private static final String TAG = "Documents";
+ // content://com.example/roots/
// content://com.example/docs/0/
// content://com.example/docs/0/contents/
- // content://com.example/search/?query=pony
+ // content://com.example/docs/0/search/?query=pony
/**
* MIME type of a document which is a directory that may contain additional
@@ -78,25 +82,69 @@ public final class DocumentsContract {
public static final int FLAG_SUPPORTS_RENAME = 1 << 1;
/**
+ * Flag indicating that a document is deletable.
+ *
+ * @see DocumentColumns#FLAGS
+ */
+ public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
+
+ /**
* Flag indicating that a document can be represented as a thumbnail.
*
* @see DocumentColumns#FLAGS
* @see #getThumbnail(ContentResolver, Uri, Point)
*/
- public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 2;
+ public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3;
+
+ /**
+ * Flag indicating that a document is a directory that supports search.
+ *
+ * @see DocumentColumns#FLAGS
+ */
+ public static final int FLAG_SUPPORTS_SEARCH = 1 << 4;
/**
* Optimal dimensions for a document thumbnail request, stored as a
* {@link Point} object. This is only a hint, and the returned thumbnail may
* have different dimensions.
+ *
+ * @see ContentProvider#openTypedAssetFile(Uri, String, Bundle)
*/
public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
+ /**
+ * Extra boolean flag included in a directory {@link Cursor#getExtras()}
+ * indicating that the backend can provide additional data if requested,
+ * such as additional search results.
+ */
+ public static final String EXTRA_HAS_MORE = "has_more";
+
+ /**
+ * Extra boolean flag included in a {@link Cursor#respond(Bundle)} call to a
+ * directory to request that additional data should be fetched. When
+ * requested data is ready, the provider should send a change notification
+ * to cause a requery.
+ *
+ * @see Cursor#respond(Bundle)
+ * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
+ * boolean)
+ */
+ public static final String EXTRA_REQUEST_MORE = "request_more";
+
+ private static final String PATH_ROOTS = "roots";
private static final String PATH_DOCS = "docs";
private static final String PATH_CONTENTS = "contents";
private static final String PATH_SEARCH = "search";
- private static final String PARAM_QUERY = "query";
+ public static final String PARAM_QUERY = "query";
+
+ /**
+ * Build URI representing the custom roots in a storage backend.
+ */
+ public static Uri buildRootsUri(String authority) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority).appendPath(PATH_ROOTS).build();
+ }
/**
* Build URI representing the given {@link DocumentColumns#GUID} in a
@@ -108,11 +156,14 @@ public final class DocumentsContract {
}
/**
- * Build URI representing a search for matching documents in a storage
- * backend.
+ * Build URI representing a search for matching documents under a directory
+ * in a storage backend.
+ *
+ * @param documentUri directory to search under, which must have
+ * {@link #FLAG_SUPPORTS_SEARCH}.
*/
- public static Uri buildSearchUri(String authority, String query) {
- return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
+ public static Uri buildSearchUri(Uri documentUri, String query) {
+ return documentUri.buildUpon()
.appendPath(PATH_SEARCH).appendQueryParameter(PARAM_QUERY, query).build();
}
@@ -134,7 +185,8 @@ public final class DocumentsContract {
public interface DocumentColumns extends OpenableColumns {
/**
* The globally unique ID for a document within a storage backend.
- * Values <em>must</em> never change once returned.
+ * Values <em>must</em> never change once returned. This field is
+ * read-only to document clients.
* <p>
* Type: STRING
*
@@ -144,7 +196,9 @@ public final class DocumentsContract {
/**
* MIME type of a document, matching the value returned by
- * {@link ContentResolver#getType(android.net.Uri)}.
+ * {@link ContentResolver#getType(android.net.Uri)}. This field must be
+ * provided when a new document is created, but after that the field is
+ * read-only.
* <p>
* Type: STRING
*
@@ -154,7 +208,8 @@ public final class DocumentsContract {
/**
* Timestamp when a document was last modified, in milliseconds since
- * January 1, 1970 00:00:00.0 UTC.
+ * January 1, 1970 00:00:00.0 UTC. This field is read-only to document
+ * clients.
* <p>
* Type: INTEGER (long)
*
@@ -163,13 +218,74 @@ public final class DocumentsContract {
public static final String LAST_MODIFIED = "last_modified";
/**
- * Flags that apply to a specific document.
+ * Flags that apply to a specific document. This field is read-only to
+ * document clients.
* <p>
* Type: INTEGER (int)
*/
public static final String FLAGS = "flags";
}
+ public static final int ROOT_TYPE_SERVICE = 1;
+ public static final int ROOT_TYPE_SHORTCUT = 2;
+ public static final int ROOT_TYPE_DEVICE = 3;
+ public static final int ROOT_TYPE_DEVICE_ADVANCED = 4;
+
+ /**
+ * These are standard columns for the roots URI.
+ *
+ * @see DocumentsContract#buildRootsUri(String)
+ */
+ public interface RootColumns {
+ /**
+ * Storage root type, use for clustering.
+ * <p>
+ * Type: INTEGER (int)
+ *
+ * @see DocumentsContract#ROOT_TYPE_SERVICE
+ * @see DocumentsContract#ROOT_TYPE_DEVICE
+ */
+ public static final String ROOT_TYPE = "root_type";
+
+ /**
+ * GUID of directory entry for this storage root.
+ * <p>
+ * Type: STRING
+ */
+ public static final String GUID = "guid";
+
+ /**
+ * Icon resource ID for this storage root, or {@code 0} to use the
+ * default {@link ProviderInfo#icon}.
+ * <p>
+ * Type: INTEGER (int)
+ */
+ public static final String ICON = "icon";
+
+ /**
+ * Title for this storage root, or {@code null} to use the default
+ * {@link ProviderInfo#labelRes}.
+ * <p>
+ * Type: STRING
+ */
+ public static final String TITLE = "title";
+
+ /**
+ * Summary for this storage root, or {@code null} to omit.
+ * <p>
+ * Type: STRING
+ */
+ public static final String SUMMARY = "summary";
+
+ /**
+ * Number of free bytes of available in this storage root, or -1 if
+ * unknown or unbounded.
+ * <p>
+ * Type: INTEGER (long)
+ */
+ public static final String AVAILABLE_BYTES = "available_bytes";
+ }
+
/**
* Return thumbnail representing the document at the given URI. Callers are
* responsible for their own caching. Given document must have
diff --git a/core/java/android/util/MapCollections.java b/core/java/android/util/MapCollections.java
index f29fb65c6ce4..09f1f8e97bd6 100644
--- a/core/java/android/util/MapCollections.java
+++ b/core/java/android/util/MapCollections.java
@@ -183,13 +183,27 @@ abstract class MapCollections<K, V> {
}
@Override
- public boolean contains(Object object) {
- throw new UnsupportedOperationException();
+ public boolean contains(Object o) {
+ if (!(o instanceof Map.Entry))
+ return false;
+ Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
+ int index = colIndexOfKey(e.getKey());
+ if (index < 0) {
+ return false;
+ }
+ Object foundVal = colGetEntry(index, 1);
+ return Objects.equal(foundVal, e.getValue());
}
@Override
public boolean containsAll(Collection<?> collection) {
- throw new UnsupportedOperationException();
+ Iterator<?> it = collection.iterator();
+ while (it.hasNext()) {
+ if (!contains(it.next())) {
+ return false;
+ }
+ }
+ return true;
}
@Override
@@ -231,6 +245,23 @@ abstract class MapCollections<K, V> {
public <T> T[] toArray(T[] array) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public boolean equals(Object object) {
+ return equalsSetHelper(this, object);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ for (int i=colGetSize()-1; i>=0; i--) {
+ final Object key = colGetEntry(i, 0);
+ final Object value = colGetEntry(i, 1);
+ result += ( (key == null ? 0 : key.hashCode()) ^
+ (value == null ? 0 : value.hashCode()) );
+ }
+ return result;
+ }
};
final class KeySet implements Set<K> {
@@ -257,7 +288,7 @@ abstract class MapCollections<K, V> {
@Override
public boolean containsAll(Collection<?> collection) {
- return removeAllHelper(colGetMap(), collection);
+ return containsAllHelper(colGetMap(), collection);
}
@Override
@@ -304,6 +335,21 @@ abstract class MapCollections<K, V> {
public <T> T[] toArray(T[] array) {
return toArrayHelper(array, 1);
}
+
+ @Override
+ public boolean equals(Object object) {
+ return equalsSetHelper(this, object);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ for (int i=colGetSize()-1; i>=0; i--) {
+ Object obj = colGetEntry(i, 0);
+ result += obj == null ? 0 : obj.hashCode();
+ }
+ return result;
+ }
};
final class ValuesCollection implements Collection<V> {
@@ -437,7 +483,6 @@ abstract class MapCollections<K, V> {
return oldSize != map.size();
}
-
public Object[] toArrayHelper(int offset) {
final int N = colGetSize();
Object[] result = new Object[N];
@@ -463,6 +508,24 @@ abstract class MapCollections<K, V> {
return array;
}
+ public static <T> boolean equalsSetHelper(Set<T> set, Object object) {
+ if (set == object) {
+ return true;
+ }
+ if (object instanceof Set) {
+ Set<?> s = (Set<?>) object;
+
+ try {
+ return set.size() == s.size() && set.containsAll(s);
+ } catch (NullPointerException ignored) {
+ return false;
+ } catch (ClassCastException ignored) {
+ return false;
+ }
+ }
+ return false;
+ }
+
public Set<Map.Entry<K, V>> getEntrySet() {
if (mEntrySet == null) {
mEntrySet = new EntrySet();
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 54c2ba565125..5f2b0494d68a 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -36,6 +36,7 @@ import android.util.AttributeSet;
import android.util.Printer;
import android.util.Slog;
import android.util.Xml;
+import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
import java.io.IOException;
import java.util.ArrayList;
@@ -162,24 +163,26 @@ public final class InputMethodInfo implements Parcelable {
}
final TypedArray a = res.obtainAttributes(
attrs, com.android.internal.R.styleable.InputMethod_Subtype);
- InputMethodSubtype subtype = new InputMethodSubtype(
- a.getResourceId(com.android.internal.R.styleable
- .InputMethod_Subtype_label, 0),
- a.getResourceId(com.android.internal.R.styleable
- .InputMethod_Subtype_icon, 0),
- a.getString(com.android.internal.R.styleable
- .InputMethod_Subtype_imeSubtypeLocale),
- a.getString(com.android.internal.R.styleable
- .InputMethod_Subtype_imeSubtypeMode),
- a.getString(com.android.internal.R.styleable
- .InputMethod_Subtype_imeSubtypeExtraValue),
- a.getBoolean(com.android.internal.R.styleable
- .InputMethod_Subtype_isAuxiliary, false),
- a.getBoolean(com.android.internal.R.styleable
- .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false),
- a.getInt(com.android.internal.R.styleable
- .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */)
- );
+ final InputMethodSubtype subtype = new InputMethodSubtypeBuilder()
+ .setSubtypeNameResId(a.getResourceId(com.android.internal.R.styleable
+ .InputMethod_Subtype_label, 0))
+ .setSubtypeIconResId(a.getResourceId(com.android.internal.R.styleable
+ .InputMethod_Subtype_icon, 0))
+ .setSubtypeLocale(a.getString(com.android.internal.R.styleable
+ .InputMethod_Subtype_imeSubtypeLocale))
+ .setSubtypeMode(a.getString(com.android.internal.R.styleable
+ .InputMethod_Subtype_imeSubtypeMode))
+ .setSubtypeExtraValue(a.getString(com.android.internal.R.styleable
+ .InputMethod_Subtype_imeSubtypeExtraValue))
+ .setIsAuxiliary(a.getBoolean(com.android.internal.R.styleable
+ .InputMethod_Subtype_isAuxiliary, false))
+ .setOverridesImplicitlyEnabledSubtype(a.getBoolean(
+ com.android.internal.R.styleable
+ .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false))
+ .setSubtypeId(a.getInt(com.android.internal.R.styleable
+ .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */))
+ .setIsAsciiCapable(a.getBoolean(com.android.internal.R.styleable
+ .InputMethod_Subtype_isAsciiCapable, false)).build();
if (!subtype.isAuxiliary()) {
mIsAuxIme = false;
}
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index 7895e6f1a3be..88b29776fa0f 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -52,6 +52,7 @@ public final class InputMethodSubtype implements Parcelable {
private final boolean mIsAuxiliary;
private final boolean mOverridesImplicitlyEnabledSubtype;
+ private final boolean mIsAsciiCapable;
private final int mSubtypeHashCode;
private final int mSubtypeIconResId;
private final int mSubtypeNameResId;
@@ -62,24 +63,145 @@ public final class InputMethodSubtype implements Parcelable {
private volatile HashMap<String, String> mExtraValueHashMapCache;
/**
+ * InputMethodSubtypeBuilder is a builder class of InputMethodSubtype.
+ * This class is designed to be used with
+ * {@link android.view.inputmethod.InputMethodManager#setAdditionalInputMethodSubtypes}.
+ * The developer needs to be aware of what each parameter means.
+ */
+ public static class InputMethodSubtypeBuilder {
+ /**
+ * @param isAuxiliary should true when this subtype is auxiliary, false otherwise.
+ * An auxiliary subtype has the following differences with a regular subtype:
+ * - An auxiliary subtype cannot be chosen as the default IME in Settings.
+ * - The framework will never switch to this subtype through
+ * {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
+ * Note that the subtype will still be available in the IME switcher.
+ * The intent is to allow for IMEs to specify they are meant to be invoked temporarily
+ * in a one-shot way, and to return to the previous IME once finished (e.g. voice input).
+ */
+ public InputMethodSubtypeBuilder setIsAuxiliary(boolean isAuxiliary) {
+ mIsAuxiliary = isAuxiliary;
+ return this;
+ }
+ private boolean mIsAuxiliary = false;
+
+ /**
+ * @param overridesImplicitlyEnabledSubtype should be true if this subtype should be
+ * enabled by default if no other subtypes in the IME are enabled explicitly. Note that a
+ * subtype with this parameter set will not be shown in the list of subtypes in each IME's
+ * subtype enabler. A canonical use of this would be for an IME to supply an "automatic"
+ * subtype that adapts to the current system language.
+ */
+ public InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(
+ boolean overridesImplicitlyEnabledSubtype) {
+ mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
+ return this;
+ }
+ private boolean mOverridesImplicitlyEnabledSubtype = false;
+
+ /**
+ * @param isAsciiCapable should be true if this subtype is ASCII capable. If the subtype
+ * is ASCII capable, it should guarantee that the user can input ASCII characters with
+ * this subtype. This is important because many password fields only allow
+ * ASCII-characters.
+ */
+ public InputMethodSubtypeBuilder setIsAsciiCapable(boolean isAsciiCapable) {
+ mIsAsciiCapable = isAsciiCapable;
+ return this;
+ }
+ private boolean mIsAsciiCapable = false;
+
+ /**
+ * @param subtypeIconResId is a resource ID of the subtype icon drawable.
+ */
+ public InputMethodSubtypeBuilder setSubtypeIconResId(int subtypeIconResId) {
+ mSubtypeIconResId = subtypeIconResId;
+ return this;
+ }
+ private int mSubtypeIconResId = 0;
+
+ /**
+ * @param subtypeNameResId is the resource ID of the subtype name string.
+ * The string resource may have exactly one %s in it. If present,
+ * the %s part will be replaced with the locale's display name by
+ * the formatter. Please refer to {@link #getDisplayName} for details.
+ */
+ public InputMethodSubtypeBuilder setSubtypeNameResId(int subtypeNameResId) {
+ mSubtypeNameResId = subtypeNameResId;
+ return this;
+ }
+ private int mSubtypeNameResId = 0;
+
+ /**
+ * @param subtypeId is the unique ID for this subtype. The input method framework keeps
+ * track of enabled subtypes by ID. When the IME package gets upgraded, enabled IDs will
+ * stay enabled even if other attributes are different. If the ID is unspecified or 0,
+ * Arrays.hashCode(new Object[] {locale, mode, extraValue,
+ * isAuxiliary, overridesImplicitlyEnabledSubtype}) will be used instead.
+ */
+ public InputMethodSubtypeBuilder setSubtypeId(int subtypeId) {
+ mSubtypeId = subtypeId;
+ return this;
+ }
+ private int mSubtypeId = 0;
+
+ /**
+ * @param subtypeLocale is the locale supported by this subtype.
+ */
+ public InputMethodSubtypeBuilder setSubtypeLocale(String subtypeLocale) {
+ mSubtypeLocale = subtypeLocale == null ? "" : subtypeLocale;
+ return this;
+ }
+ private String mSubtypeLocale = "";
+
+ /**
+ * @param subtypeMode is the mode supported by this subtype.
+ */
+ public InputMethodSubtypeBuilder setSubtypeMode(String subtypeMode) {
+ mSubtypeMode = subtypeMode == null ? "" : subtypeMode;
+ return this;
+ }
+ private String mSubtypeMode = "";
+ /**
+ * @param subtypeExtraValue is the extra value of the subtype. This string is free-form,
+ * but the API supplies tools to deal with a key-value comma-separated list; see
+ * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
+ */
+ public InputMethodSubtypeBuilder setSubtypeExtraValue(String subtypeExtraValue) {
+ mSubtypeExtraValue = subtypeExtraValue == null ? "" : subtypeExtraValue;
+ return this;
+ }
+ private String mSubtypeExtraValue = "";
+
+ /**
+ * @return InputMethodSubtype using parameters in this InputMethodSubtypeBuilder.
+ */
+ public InputMethodSubtype build() {
+ return new InputMethodSubtype(this);
+ }
+ }
+
+ private static InputMethodSubtypeBuilder getBuilder(int nameId, int iconId, String locale,
+ String mode, String extraValue, boolean isAuxiliary,
+ boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable) {
+ final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
+ builder.mSubtypeNameResId = nameId;
+ builder.mSubtypeIconResId = iconId;
+ builder.mSubtypeLocale = locale;
+ builder.mSubtypeMode = mode;
+ builder.mSubtypeExtraValue = extraValue;
+ builder.mIsAuxiliary = isAuxiliary;
+ builder.mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
+ builder.mSubtypeId = id;
+ builder.mIsAsciiCapable = isAsciiCapable;
+ return builder;
+ }
+
+ /**
* Constructor with no subtype ID specified, overridesImplicitlyEnabledSubtype not specified.
- * @param nameId Resource ID of the subtype name string. The string resource may have exactly
- * one %s in it. If there is, the %s part will be replaced with the locale's display name by
- * the formatter. Please refer to {@link #getDisplayName} for details.
- * @param iconId Resource ID of the subtype icon drawable.
- * @param locale The locale supported by the subtype
- * @param mode The mode supported by the subtype
- * @param extraValue The extra value of the subtype. This string is free-form, but the API
- * supplies tools to deal with a key-value comma-separated list; see
- * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
- * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary
- * subtype will not be shown in the list of enabled IMEs for choosing the current IME in
- * the Settings even when this subtype is enabled. Please note that this subtype will still
- * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch
- * to this subtype while an IME is shown. The framework will never switch the current IME to
- * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
- * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
- * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
+ * Arguments for this constructor have the same meanings as
+ * {@link InputMethodSubtype#InputMethodSubtype(int, int, String, String, String, boolean,
+ * boolean, int)} except "id" and "overridesImplicitlyEnabledSubtype".
* @hide
*/
public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
@@ -89,27 +211,10 @@ public final class InputMethodSubtype implements Parcelable {
/**
* Constructor with no subtype ID specified.
- * @param nameId Resource ID of the subtype name string. The string resource may have exactly
- * one %s in it. If there is, the %s part will be replaced with the locale's display name by
- * the formatter. Please refer to {@link #getDisplayName} for details.
- * @param iconId Resource ID of the subtype icon drawable.
- * @param locale The locale supported by the subtype
- * @param mode The mode supported by the subtype
- * @param extraValue The extra value of the subtype. This string is free-form, but the API
- * supplies tools to deal with a key-value comma-separated list; see
- * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
- * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary
- * subtype will not be shown in the list of enabled IMEs for choosing the current IME in
- * the Settings even when this subtype is enabled. Please note that this subtype will still
- * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch
- * to this subtype while an IME is shown. The framework will never switch the current IME to
- * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
- * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
- * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
- * @param overridesImplicitlyEnabledSubtype true when this subtype should be enabled by default
- * if no other subtypes in the IME are enabled explicitly. Note that a subtype with this
- * parameter being true will not be shown in the list of subtypes in each IME's subtype enabler.
- * Having an "automatic" subtype is an example use of this flag.
+ * @deprecated use {@link InputMethodSubtypeBuilder} instead.
+ * Arguments for this constructor have the same meanings as
+ * {@link InputMethodSubtype#InputMethodSubtype(int, int, String, String, String, boolean,
+ * boolean, int)} except "id".
*/
public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
@@ -119,6 +224,8 @@ public final class InputMethodSubtype implements Parcelable {
/**
* Constructor.
+ * @deprecated use {@link InputMethodSubtypeBuilder} instead.
+ * "isAsciiCapable" is "false" in this constructor.
* @param nameId Resource ID of the subtype name string. The string resource may have exactly
* one %s in it. If there is, the %s part will be replaced with the locale's display name by
* the formatter. Please refer to {@link #getDisplayName} for details.
@@ -148,18 +255,29 @@ public final class InputMethodSubtype implements Parcelable {
*/
public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id) {
- mSubtypeNameResId = nameId;
- mSubtypeIconResId = iconId;
- mSubtypeLocale = locale != null ? locale : "";
- mSubtypeMode = mode != null ? mode : "";
- mSubtypeExtraValue = extraValue != null ? extraValue : "";
- mIsAuxiliary = isAuxiliary;
- mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
+ this(getBuilder(nameId, iconId, locale, mode, extraValue, isAuxiliary,
+ overridesImplicitlyEnabledSubtype, id, false));
+ }
+
+ /**
+ * Constructor.
+ * @param builder Builder for InputMethodSubtype
+ */
+ private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
+ mSubtypeNameResId = builder.mSubtypeNameResId;
+ mSubtypeIconResId = builder.mSubtypeIconResId;
+ mSubtypeLocale = builder.mSubtypeLocale;
+ mSubtypeMode = builder.mSubtypeMode;
+ mSubtypeExtraValue = builder.mSubtypeExtraValue;
+ mIsAuxiliary = builder.mIsAuxiliary;
+ mOverridesImplicitlyEnabledSubtype = builder.mOverridesImplicitlyEnabledSubtype;
+ mSubtypeId = builder.mSubtypeId;
+ mIsAsciiCapable = builder.mIsAsciiCapable;
// If hashCode() of this subtype is 0 and you want to specify it as an id of this subtype,
// just specify 0 as this subtype's id. Then, this subtype's id is treated as 0.
- mSubtypeHashCode = id != 0 ? id : hashCodeInternal(mSubtypeLocale, mSubtypeMode,
- mSubtypeExtraValue, mIsAuxiliary, mOverridesImplicitlyEnabledSubtype);
- mSubtypeId = id;
+ mSubtypeHashCode = mSubtypeId != 0 ? mSubtypeId : hashCodeInternal(mSubtypeLocale,
+ mSubtypeMode, mSubtypeExtraValue, mIsAuxiliary, mOverridesImplicitlyEnabledSubtype,
+ mIsAsciiCapable);
}
InputMethodSubtype(Parcel source) {
@@ -176,6 +294,7 @@ public final class InputMethodSubtype implements Parcelable {
mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1);
mSubtypeHashCode = source.readInt();
mSubtypeId = source.readInt();
+ mIsAsciiCapable = (source.readInt() == 1);
}
/**
@@ -239,6 +358,15 @@ public final class InputMethodSubtype implements Parcelable {
}
/**
+ * @return true if this subtype is Ascii capable, false otherwise. If the subtype is ASCII
+ * capable, it should guarantee that the user can input ASCII characters with this subtype.
+ * This is important because many password fields only allow ASCII-characters.
+ */
+ public boolean isAsciiCapable() {
+ return mIsAsciiCapable;
+ }
+
+ /**
* @param context Context will be used for getting Locale and PackageManager.
* @param packageName The package name of the IME
* @param appInfo The application info of the IME
@@ -336,7 +464,8 @@ public final class InputMethodSubtype implements Parcelable {
&& (subtype.getIconResId() == getIconResId())
&& (subtype.getLocale().equals(getLocale()))
&& (subtype.getExtraValue().equals(getExtraValue()))
- && (subtype.isAuxiliary() == isAuxiliary());
+ && (subtype.isAuxiliary() == isAuxiliary())
+ && (subtype.isAsciiCapable() == isAsciiCapable());
}
return false;
}
@@ -357,6 +486,7 @@ public final class InputMethodSubtype implements Parcelable {
dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0);
dest.writeInt(mSubtypeHashCode);
dest.writeInt(mSubtypeId);
+ dest.writeInt(mIsAsciiCapable ? 1 : 0);
}
public static final Parcelable.Creator<InputMethodSubtype> CREATOR
@@ -389,9 +519,10 @@ public final class InputMethodSubtype implements Parcelable {
}
private static int hashCodeInternal(String locale, String mode, String extraValue,
- boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
+ boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
+ boolean isAsciiCapable) {
return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
- overridesImplicitlyEnabledSubtype});
+ overridesImplicitlyEnabledSubtype, isAsciiCapable});
}
/**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 4f501e7dbf3d..a6e68d0aba54 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -862,7 +862,6 @@ public class WebView extends AbsoluteLayout
* @param resultCallback A callback to be invoked when the script execution
* completes with the result of the execution (if any).
* May be null if no notificaion of the result is required.
- * @hide pending API council approval and CTS test coverage.
*/
public void evaluateJavascript(String script, ValueCallback<String> resultCallback) {
checkThread();
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 53abf196a699..d924447907fc 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -82,6 +82,8 @@ public final class WebViewFactory {
public static void setUseExperimentalWebView(boolean enable) {
SystemProperties.set(WebViewFactory.WEBVIEW_EXPERIMENTAL_PROPERTY,
enable ? "true" : "false");
+ Log.i(LOGTAG, "Use Experimental WebView changed: "
+ + SystemProperties.get(WebViewFactory.WEBVIEW_EXPERIMENTAL_PROPERTY, ""));
}
/** @hide */
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index 345a39e8cbac..c7b5697f55af 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -57,6 +57,45 @@ public class InputMethodUtils {
// This utility class is not publicly instantiable.
}
+ // ----------------------------------------------------------------------
+ // Utilities for debug
+ public static String getStackTrace() {
+ final StringBuilder sb = new StringBuilder();
+ try {
+ throw new RuntimeException();
+ } catch (RuntimeException e) {
+ final StackTraceElement[] frames = e.getStackTrace();
+ // Start at 1 because the first frame is here and we don't care about it
+ for (int j = 1; j < frames.length; ++j) {
+ sb.append(frames[j].toString() + "\n");
+ }
+ }
+ return sb.toString();
+ }
+
+ public static String getApiCallStack() {
+ String apiCallStack = "";
+ try {
+ throw new RuntimeException();
+ } catch (RuntimeException e) {
+ final StackTraceElement[] frames = e.getStackTrace();
+ for (int j = 1; j < frames.length; ++j) {
+ final String tempCallStack = frames[j].toString();
+ if (TextUtils.isEmpty(apiCallStack)) {
+ // Overwrite apiCallStack if it's empty
+ apiCallStack = tempCallStack;
+ } else if (tempCallStack.indexOf("Transact(") < 0) {
+ // Overwrite apiCallStack if it's not a binder call
+ apiCallStack = tempCallStack;
+ } else {
+ break;
+ }
+ }
+ }
+ return apiCallStack;
+ }
+ // ----------------------------------------------------------------------
+
public static boolean isSystemIme(InputMethodInfo inputMethod) {
return (inputMethod.getServiceInfo().applicationInfo.flags
& ApplicationInfo.FLAG_SYSTEM) != 0;
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 32f6ecf7cda3..2c23f9db9468 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -586,7 +586,7 @@ static jstring android_content_AssetManager_getResourceName(JNIEnv* env, jobject
}
ResTable::resource_name name;
- if (!am->getResources().getResourceName(resid, &name)) {
+ if (!am->getResources().getResourceName(resid, true, &name)) {
return NULL;
}
@@ -594,19 +594,27 @@ static jstring android_content_AssetManager_getResourceName(JNIEnv* env, jobject
if (name.package != NULL) {
str.setTo(name.package, name.packageLen);
}
- if (name.type != NULL) {
+ if (name.type8 != NULL || name.type != NULL) {
if (str.size() > 0) {
char16_t div = ':';
str.append(&div, 1);
}
- str.append(name.type, name.typeLen);
+ if (name.type8 != NULL) {
+ str.append(String16(name.type8, name.typeLen));
+ } else {
+ str.append(name.type, name.typeLen);
+ }
}
- if (name.name != NULL) {
+ if (name.name8 != NULL || name.name != NULL) {
if (str.size() > 0) {
char16_t div = '/';
str.append(&div, 1);
}
- str.append(name.name, name.nameLen);
+ if (name.name8 != NULL) {
+ str.append(String16(name.name8, name.nameLen));
+ } else {
+ str.append(name.name, name.nameLen);
+ }
}
return env->NewString((const jchar*)str.string(), str.size());
@@ -621,7 +629,7 @@ static jstring android_content_AssetManager_getResourcePackageName(JNIEnv* env,
}
ResTable::resource_name name;
- if (!am->getResources().getResourceName(resid, &name)) {
+ if (!am->getResources().getResourceName(resid, true, &name)) {
return NULL;
}
@@ -641,10 +649,14 @@ static jstring android_content_AssetManager_getResourceTypeName(JNIEnv* env, job
}
ResTable::resource_name name;
- if (!am->getResources().getResourceName(resid, &name)) {
+ if (!am->getResources().getResourceName(resid, true, &name)) {
return NULL;
}
+ if (name.type8 != NULL) {
+ return env->NewStringUTF(name.type8);
+ }
+
if (name.type != NULL) {
return env->NewString((const jchar*)name.type, name.typeLen);
}
@@ -661,10 +673,14 @@ static jstring android_content_AssetManager_getResourceEntryName(JNIEnv* env, jo
}
ResTable::resource_name name;
- if (!am->getResources().getResourceName(resid, &name)) {
+ if (!am->getResources().getResourceName(resid, true, &name)) {
return NULL;
}
+ if (name.name8 != NULL) {
+ return env->NewStringUTF(name.name8);
+ }
+
if (name.name != NULL) {
return env->NewString((const jchar*)name.name, name.nameLen);
}
@@ -680,7 +696,7 @@ static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject
{
if (outValue == NULL) {
jniThrowNullPointerException(env, "outValue");
- return NULL;
+ return 0;
}
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 6d97d01b4b86..8325217cc9fd 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -95,26 +95,6 @@ static void signalExceptionForGroupError(JNIEnv* env, int err)
}
}
-jint android_os_Process_myPid(JNIEnv* env, jobject clazz)
-{
- return getpid();
-}
-
-jint android_os_Process_myPpid(JNIEnv* env, jobject clazz)
-{
- return getppid();
-}
-
-jint android_os_Process_myUid(JNIEnv* env, jobject clazz)
-{
- return getuid();
-}
-
-jint android_os_Process_myTid(JNIEnv* env, jobject clazz)
-{
- return androidGetTid();
-}
-
jint android_os_Process_getUidForName(JNIEnv* env, jobject clazz, jstring name)
{
if (name == NULL) {
@@ -349,8 +329,7 @@ void android_os_Process_setThreadPriority(JNIEnv* env, jobject clazz,
void android_os_Process_setCallingThreadPriority(JNIEnv* env, jobject clazz,
jint pri)
{
- jint tid = android_os_Process_myTid(env, clazz);
- android_os_Process_setThreadPriority(env, clazz, tid, pri);
+ android_os_Process_setThreadPriority(env, clazz, androidGetTid(), pri);
}
jint android_os_Process_getThreadPriority(JNIEnv* env, jobject clazz,
@@ -1034,10 +1013,6 @@ jintArray android_os_Process_getPidsForCommands(JNIEnv* env, jobject clazz,
}
static const JNINativeMethod methods[] = {
- {"myPid", "()I", (void*)android_os_Process_myPid},
- {"myPpid", "()I", (void*)android_os_Process_myPpid},
- {"myTid", "()I", (void*)android_os_Process_myTid},
- {"myUid", "()I", (void*)android_os_Process_myUid},
{"getUidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getUidForName},
{"getGidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getGidForName},
{"setThreadPriority", "(II)V", (void*)android_os_Process_setThreadPriority},
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 15edacd7f41f..4cad23231b59 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2407,6 +2407,11 @@
constructor or 0. Arrays.hashCode(new Object[] {locale, mode, extraValue,
isAuxiliary, overridesImplicitlyEnabledSubtype}) will be used instead. -->
<attr name="subtypeId" format="integer"/>
+ <!-- Set to true if this subtype is ASCII capable. If the subtype is ASCII
+ capable, it should guarantee that the user can input ASCII characters with
+ this subtype. This is important because many password fields only allow
+ ASCII-characters. -->
+ <attr name="isAsciiCapable" format="boolean" />
</declare-styleable>
<!-- Use <code>spell-checker</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 4a04d3e1898c..b9c36382ddaf 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2069,4 +2069,5 @@
<public type="attr" name="addPrintersActivity" />
<public type="attr" name="vendor" />
<public type="attr" name="category" />
+ <public type="attr" name="isAsciiCapable" />
</resources>
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index d6a7ee27a2b7..d8290f494b3e 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -273,28 +273,47 @@ public class LinkPropertiesTest extends TestCase {
// Check comparisons work.
LinkProperties lp2 = new LinkProperties(lp);
assertAllRoutesHaveInterface("wlan0", lp);
- assertEquals(0, lp.compareRoutes(lp2).added.size());
- assertEquals(0, lp.compareRoutes(lp2).removed.size());
+ assertEquals(0, lp.compareAllRoutes(lp2).added.size());
+ assertEquals(0, lp.compareAllRoutes(lp2).removed.size());
lp2.setInterfaceName("p2p0");
assertAllRoutesHaveInterface("p2p0", lp2);
- assertEquals(3, lp.compareRoutes(lp2).added.size());
- assertEquals(3, lp.compareRoutes(lp2).removed.size());
+ assertEquals(3, lp.compareAllRoutes(lp2).added.size());
+ assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
}
@SmallTest
public void testStackedInterfaces() {
LinkProperties rmnet0 = new LinkProperties();
rmnet0.setInterfaceName("rmnet0");
+ rmnet0.addLinkAddress(new LinkAddress(
+ NetworkUtils.numericToInetAddress(ADDRV6), 128));
LinkProperties clat4 = new LinkProperties();
clat4.setInterfaceName("clat4");
+ clat4.addLinkAddress(new LinkAddress(
+ NetworkUtils.numericToInetAddress(ADDRV4), 32));
assertEquals(0, rmnet0.getStackedLinks().size());
+ assertEquals(1, rmnet0.getAddresses().size());
+ assertEquals(1, rmnet0.getLinkAddresses().size());
+ assertEquals(1, rmnet0.getAllAddresses().size());
+ assertEquals(1, rmnet0.getAllLinkAddresses().size());
+
rmnet0.addStackedLink(clat4);
assertEquals(1, rmnet0.getStackedLinks().size());
+ assertEquals(1, rmnet0.getAddresses().size());
+ assertEquals(1, rmnet0.getLinkAddresses().size());
+ assertEquals(2, rmnet0.getAllAddresses().size());
+ assertEquals(2, rmnet0.getAllLinkAddresses().size());
+
rmnet0.addStackedLink(clat4);
assertEquals(1, rmnet0.getStackedLinks().size());
+ assertEquals(1, rmnet0.getAddresses().size());
+ assertEquals(1, rmnet0.getLinkAddresses().size());
+ assertEquals(2, rmnet0.getAllAddresses().size());
+ assertEquals(2, rmnet0.getAllLinkAddresses().size());
+
assertEquals(0, clat4.getStackedLinks().size());
// Modify an item in the returned collection to see what happens.
diff --git a/docs/html/_redirects.yaml b/docs/html/_redirects.yaml
index 2169c8ed13be..c23ee101dfab 100644
--- a/docs/html/_redirects.yaml
+++ b/docs/html/_redirects.yaml
@@ -299,6 +299,9 @@ redirects:
# ------------------- TRAINING -------------------
+- from: /guide/topics/ui/layout/tabs.html
+ to: /training/implementing-navigation/lateral.html
+
- from: /training/cloudsync/aesync.html
to: /google/gcm/index.html
diff --git a/docs/html/about/versions/jelly-bean.jd b/docs/html/about/versions/jelly-bean.jd
index 985a26634b17..5deb1900106d 100644
--- a/docs/html/about/versions/jelly-bean.jd
+++ b/docs/html/about/versions/jelly-bean.jd
@@ -7,14 +7,7 @@ tab3=Android 4.1
tab3.link=#android-41
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
+
<style>
#android-41 {display:none;}
diff --git a/docs/html/channels/io2013.jd b/docs/html/channels/io2013.jd
index b2bde312aa4f..977eb2f2aed3 100644
--- a/docs/html/channels/io2013.jd
+++ b/docs/html/channels/io2013.jd
@@ -1,15 +1,7 @@
fullpage=true
page.title=Google I/O 13
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
-
+
<style>
#ioplayer-frame {
z-index:10;
diff --git a/docs/html/design/downloads/index.jd b/docs/html/design/downloads/index.jd
index ab6bb1b67636..00f44677d8dd 100644
--- a/docs/html/design/downloads/index.jd
+++ b/docs/html/design/downloads/index.jd
@@ -1,13 +1,5 @@
page.title=Downloads
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
<div class="layout-content-row">
<div class="layout-content-col span-9">
diff --git a/docs/html/design/index.jd b/docs/html/design/index.jd
index d4ef07fd0c8f..1e6b40c5876b 100644
--- a/docs/html/design/index.jd
+++ b/docs/html/design/index.jd
@@ -2,14 +2,6 @@ page.title=Design
header.hide=1
footer.hide=1
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
<style>
#landing-graphic-container {
diff --git a/docs/html/develop/index.jd b/docs/html/develop/index.jd
index 7e2337c28f3c..1833f24e2e75 100644
--- a/docs/html/develop/index.jd
+++ b/docs/html/develop/index.jd
@@ -4,14 +4,6 @@ header.hide=1
carousel=1
tabbedList=1
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
<style>
#noplayer-message {
@@ -140,7 +132,7 @@ more</a></p>
<li><a href="//android-developers.blogspot.com/2013/07/beautiful-design-collection-summer-2013.html">
<div class="feed-image" style="background:url('//1.bp.blogspot.com/-k8DZYu0daT4/UdRt1AzstvI/AAAAAAAAAFM/CvEkb2yh-i0/s965/beautifulapps_4.png') no-repeat 0 0"></div>
<h4>The Beautiful Design Summer 2013 Collection</h4>
- <p>See the apps chosen by the Android Design for their masterfully crafted design details...</p>
+ <p>See the apps chosen by the Android Design team for their masterfully crafted design details...</p>
</a></li>
<li><a href="//android-developers.blogspot.com/2013/06/google-play-developer-8-step-checkup.html">
<div class="feed-image" style="background:url('//4.bp.blogspot.com/-LeK74UYY1eM/UbD8L-2DpFI/AAAAAAAACZA/YMjwndr-ZgM/s400/DoctorDroidV2.png') no-repeat 0 0;background-size:130px;background-position:8px -4px;"></div>
diff --git a/docs/html/distribute/distribute_toc.cs b/docs/html/distribute/distribute_toc.cs
index 75cf9f93d13e..ecdf2a842301 100644
--- a/docs/html/distribute/distribute_toc.cs
+++ b/docs/html/distribute/distribute_toc.cs
@@ -80,6 +80,7 @@
<div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/googleplay/spotlight/index.html">Spotlight</a></div>
<ul>
<li><a href="<?cs var:toroot ?>distribute/googleplay/spotlight/tablets.html">Tablet Stories</a></li>
+ <li><a href="<?cs var:toroot ?>distribute/googleplay/spotlight/games.html">Game Stories</a></li>
</ul>
</li>
diff --git a/docs/html/distribute/googleplay/promote/badges.jd b/docs/html/distribute/googleplay/promote/badges.jd
index 93092bfcb66e..9a32921e2cbf 100644
--- a/docs/html/distribute/googleplay/promote/badges.jd
+++ b/docs/html/distribute/googleplay/promote/badges.jd
@@ -1,13 +1,5 @@
page.title=Google Play Badges
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
<p itemprop="description">Google Play badges allow you to promote your app with official branding
in your online ads, promotional materials, or anywhere else you want a link to your app.</p>
diff --git a/docs/html/distribute/googleplay/promote/brand.jd b/docs/html/distribute/googleplay/promote/brand.jd
index a047b1fa098a..265584f6fa83 100644
--- a/docs/html/distribute/googleplay/promote/brand.jd
+++ b/docs/html/distribute/googleplay/promote/brand.jd
@@ -1,13 +1,6 @@
page.title=Brand Guidelines
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
+
<p>We encourage you to use the Android and Google Play brands with your Android app
diff --git a/docs/html/distribute/googleplay/promote/index.jd b/docs/html/distribute/googleplay/promote/index.jd
index 14f37c4d1b5c..68829907fe22 100644
--- a/docs/html/distribute/googleplay/promote/index.jd
+++ b/docs/html/distribute/googleplay/promote/index.jd
@@ -3,14 +3,6 @@ page.metaDescription=Raise the visibility of your apps in Google Play through de
header.hide=0
footer.hide=0
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
<!--
<style>
diff --git a/docs/html/distribute/googleplay/promote/linking.jd b/docs/html/distribute/googleplay/promote/linking.jd
index 014582a39e43..4fdc5dba1a9a 100644
--- a/docs/html/distribute/googleplay/promote/linking.jd
+++ b/docs/html/distribute/googleplay/promote/linking.jd
@@ -1,13 +1,5 @@
page.title=Linking to Your Products
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
<div class="sidebox-wrapper">
<div class="sidebox">
diff --git a/docs/html/distribute/googleplay/publish/preparing.jd b/docs/html/distribute/googleplay/publish/preparing.jd
index dd35b25aeb6a..5593f4f8efc9 100644
--- a/docs/html/distribute/googleplay/publish/preparing.jd
+++ b/docs/html/distribute/googleplay/publish/preparing.jd
@@ -1,14 +1,6 @@
page.title=Launch Checklist
page.tags="publishing","launch","Google Play", "Developer Console"
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
<div id="qv-wrapper"><div id="qv">
<h2>Checklist</h2>
diff --git a/docs/html/distribute/googleplay/quality/core.jd b/docs/html/distribute/googleplay/quality/core.jd
index 3fd221cddda4..9e23bcca28a5 100644
--- a/docs/html/distribute/googleplay/quality/core.jd
+++ b/docs/html/distribute/googleplay/quality/core.jd
@@ -1,13 +1,5 @@
page.title=Core App Quality Guidelines
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
<div id="qv-wrapper"><div id="qv">
<h2>Quality Criteria</h2>
diff --git a/docs/html/distribute/googleplay/quality/index.jd b/docs/html/distribute/googleplay/quality/index.jd
index def42e503950..ef537b14f8e7 100644
--- a/docs/html/distribute/googleplay/quality/index.jd
+++ b/docs/html/distribute/googleplay/quality/index.jd
@@ -1,13 +1,5 @@
page.title=App Quality
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
<p>App quality directly influences the long-term success of your app&mdash;in
terms of installs, user rating and reviews, engagement, and user retention.
diff --git a/docs/html/distribute/googleplay/quality/tablet.jd b/docs/html/distribute/googleplay/quality/tablet.jd
index c80c3cc978ba..36e7345976bd 100644
--- a/docs/html/distribute/googleplay/quality/tablet.jd
+++ b/docs/html/distribute/googleplay/quality/tablet.jd
@@ -1,13 +1,5 @@
page.title=Tablet App Quality Checklist
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
<div id="qv-wrapper"><div id="qv">
<h2>Checklist</h2>
@@ -312,7 +304,7 @@ without blurring or other scaling artifacts.</p>
<td>144x144 px</td>
<td>96x96 px</td>
<td>48x48 px</td>
-<td>74x74 px</td>
+<td>72x72 px</td>
</tr>
</table>
diff --git a/docs/html/distribute/googleplay/spotlight/games.jd b/docs/html/distribute/googleplay/spotlight/games.jd
new file mode 100644
index 000000000000..4e356dbe7afa
--- /dev/null
+++ b/docs/html/distribute/googleplay/spotlight/games.jd
@@ -0,0 +1,245 @@
+page.title=Developer Stories: Google Play Game Services
+walkthru=0
+header.hide=0
+
+@jd:body
+
+<p>One of the goals of <a href="https://developers.google.com/games/">Google
+Play game services</a> is to allow developers to focus on what they’re good at
+as game developers &mdash; creating great gaming experiences for their users, by
+building on top of what Google is good at: mobile and cloud services. Integral
+to that is an easy integration process, one that provides a whole host of
+features with little engineering work required.</p>
+
+<p>The gaming studios below understood the opportunity that Google Play game
+services unlocked, and are starting to see real results following their
+successful integrations. </p>
+
+<div style="margin-bottom:2em;"><!-- START STORY -->
+
+<h3>Concrete Software &mdash; Straightforward, easy to implement</h3>
+
+<img alt="" class="screenshot thumbnail" style="-webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px height:78px;
+ width: 78px;
+ float: left;
+ margin: 12px 20px 9px 20px;"
+ src="//lh6.ggpht.com/_UOay5HBxf077suKYzmikU2IbnYOJub3X0inz-LoUsVh4TX758BEyArjoR7owXijkAA=w124">
+
+<div style="list-style: none;height:100%;
+ float: right;
+ border-top: 1px solid #9C0;
+ width: 220px;
+ margin: 4px 20px;padding: .5em;">
+
+ <h5>About the developer</h5>
+ <ul>
+ <li><a href="https://play.google.com/store/apps/developer?id=Concrete%20Software%2C%20Inc.">Concrete Software</a>,
+ makers of <a href="https://play.google.com/store/apps/details?id=com.concretesoftware.pbachallenge_androidmarket&hl=en">PBA
+ Bowling Challenge</a></li>
+ <li>Added support for multiplayer, leaderboards and achievements through Google Play game
+ services</li>
+ </ul>
+
+ <h5>Results</h5>
+ <ul>
+ <li>Session lengths have increased more than 15%</li>
+ </ul>
+
+ <div style="padding:.5em 0 0 1em;">
+ <a href="https://play.google.com/store/apps/details?id=com.concretesoftware.pbachallenge_androidmarket&hl=en">
+ <img alt="Android app on Google Play" src="//developer.android.com/images/brand/en_generic_rgb_wo_45.png" />
+ </a>
+ </div>
+</div>
+
+<div style="line-height:1.4em;">
+<p style="margin-top:0;margin-bottom:12px;">Concrete Software added several
+features from Google Play game services into one of their top titles,
+<a href="https://play.google.com/store/apps/details?id=com.concretesoftware.pbachallenge_androidmarket">PBA
+Bowling Challenge</a>, including support for multiplayer, leaderboards, and
+achievements.</p>
+
+<p>So far, their users have loved the new additions: average session length
+is up more than 15%. Keith Pichelman, CEO of Concrete Software, explains: </p>
+
+<p>"The Google Play game services were straightforward and easy to implement. We
+had been researching options for multiplayer services, so when Google Play game
+services came out, it was an easy decision for us. Not only were they easy to
+integrate, but the features have worked flawlessly. </p>
+
+<p>"PBA Bowling Challenge now has real-time multiplayer which our users instantly
+were thrilled with; you can see in the reviews how people immediately raved about
+the new game experience. </p>
+
+<p>"We also included achievements, leaderboards, and most recently cloud
+synchronization from the Google Play game services as well. Using the game
+services in PBA Bowling Challenge was a huge success, enough so that we are now
+going back to our other titles, adding the features to them as well."</p>
+</div>
+
+<div style="clear:both;margin-top:40px;width:auto;">
+
+ <img src="{@docRoot}images/distribute/concrete-pbc-gpgames.jpg">
+
+ <div style="width:600px;margin-top:0px;padding:0 90px;">
+ <p class="image-caption"><span style="font-weight:500;">Session lengths up:</span>
+ After adding support for multiplayer with Google Play game services, Concrete
+ Software saw an increase in session lengths of more than 15% for PBA Bowling
+ Challenge.</p>
+ </div>
+</div>
+</div> <!-- END STORY -->
+
+<div style="margin:3em auto"><!-- START STORY -->
+
+<h3>Glu: It’s a must-have for all titles</h3>
+
+<img alt="" class="screenshot thumbnail" style="-webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px height:78px;
+ width: 78px;
+ float: left;
+ margin: 12px 20px 30px 20px;"
+ src="//lh5.ggpht.com/l20dR2HYLV8vECoC35q_0NdfaAGTe4lZIFy_wCJRDqZjeQqSgneLRpXi3qOnnCaLXA=w124">
+
+<div style="list-style: none;height:100%;
+ float: right;
+ border-top: 1px solid #9C0;
+ width: 220px;
+ margin: 4px 20px;padding: .5em;">
+
+ <h5>About the developer</h5>
+ <ul>
+ <li><a href="https://play.google.com/store/apps/developer?id=Glu+Mobile">Glu
+ Mobile</a>, creators of <a href="https://play.google.com/store/apps/details?id=com.glu.ewarriors2">Eternity
+ Warriors 2</a></li>
+ <li>Has already integrated 5 titles with Google Play game services</li>
+ </ul>
+
+ <h5>Results</h5>
+ <ul>
+ <li>In Eternity Warriors 2, 7-day user retention is up 40%</li>
+ <li>20% increase in play sessions per day as well</li>
+ </ul>
+
+ <div style="padding:.5em 0 0 1em;">
+ <a href="https://play.google.com/store/apps/details?id=com.glu.ewarriors2">
+ <img alt="Android app on Google Play" src="//developer.android.com/images/brand/en_generic_rgb_wo_45.png" />
+ </a>
+ </div>
+</div>
+
+<div style="line-height:1.4em;">
+<p style="margin-top:0;margin-bottom:12px;">Glu was one of the first developers
+to integrate Google Play game services, with
+<a href="https://play.google.com/store/apps/details?id=com.glu.ewarriors2">Eternity
+Warriors 2</a>. Based on this first success, Glu has integrated game services
+into several more games, including Samurai vs. Zombies 2, Frontline Commando:
+D-Day, Contract Killer 2, and Zombies Ate My Friends.</p>
+
+<p>Already supported in Eternity Warriors 2, they’ve seen a 40% increase in 7-day
+user retention and a 20% increase in play sessions per day. Sourabh Ahuja, Glu's
+Vice President of Android Development, explains:</p>
+
+<p>“Multiplayer, leaderboards, achievements &mdash; these are all things that we
+had to build individually for our titles. The availability of these features in
+Google Play game services helps us make our games stickier, and it’s awesome that
+it comes directly from Google. </p>
+
+<p>"It’s flexible enough that we were able to make it interoperable with our
+in-house systems. We look forward to utilizing game services extensively across
+our portfolio."</p>
+</div>
+
+<div style="clear:both;margin-top:40px;width:auto;">
+
+ <img src="{@docRoot}images/distribute/glu-ew-gpgames.jpg"></a>
+
+ <div style="width:600px;margin-top:0px;padding:0 90px;">
+ <p class="image-caption"><span style="font-weight:500;">User retention up:</span>
+ Glu saw a 40% increase in 7-day user retention for Eternity Warriors 2 after
+ integrating with Google Play game services.</p>
+ </div>
+</div>
+</div> <!-- END STORY -->
+
+
+<div style="margin-bottom:2em;"><!-- START STORY -->
+
+<h3>Vector-Unit: An awesome multiplayer experience</h3>
+
+<img alt="" class="screenshot thumbnail" style="-webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px height:78px;
+ width: 78px;
+ float: left;
+ margin: 12px 20px 9px 20px;" src=
+ "https://lh3.ggpht.com/dTUrKLffqXHJtPuIlp8fjDhROuzrTcpidbNFprugR65hMrPLX7Omd8SGop0xMXXKzcw=w124">
+
+<div style="list-style: none;height:100%;
+ float: right;
+ border-top: 1px solid #9C0;
+ width: 220px;
+ margin: 4px 20px;padding: .5em;">
+
+ <h5>About the developer</h5>
+ <ul>
+ <li><a href="https://play.google.com/store/apps/developer?id=Vector+Unit">Vector
+ Unit</a>, creators of <a href="https://play.google.com/store/apps/details?id=com.vectorunit.red">Riptide
+ GP2</a></li>
+ <li>Added multiplayer to Riptide GP2 through Google Play game services </li>
+ </ul>
+
+ <h5>Results</h5>
+ <ul>
+ <li>With an easy multiplayer solution, they were able to focus on the
+ gameplay</li>
+ <li>Early reviews of Riptide GP2 called multiplayer “one of the sweetest
+ cherries on top!”</li>
+ </ul>
+
+ <div style="padding:.5em 0 0 1em;">
+ <a href="https://play.google.com/store/apps/details?id=com.vectorunit.red">
+ <img alt="Android app on Google Play" src="//developer.android.com/images/brand/en_generic_rgb_wo_45.png" />
+ </a>
+ </div>
+</div>
+
+<div style="line-height:1.4em;">
+<p style="margin-top:0;margin-bottom:12px;">Vector Unit just launched their
+latest title, <a href="https://play.google.com/store/apps/details?id=com.vectorunit.red">Riptide
+GP2</a>, with Google Play game services integration, and it has one of the strongest
+integrations of multiplayer yet. Early reviews call multiplayer “one of the sweetest
+cherries on top!”.</p>
+
+<p>Ralf Knoesel, CTO of Vector Unit, tells more about how they've used Google Play game
+services:</p>
+
+<p>“We wanted to provide a really compelling multiplayer experience for our users, and
+Google Play game services allowed us to do just that. With multiplayer, you can show off
+your skills and your custom-tuned hydro jet in 4-way online battles with friends and
+players around the world. </p>
+
+<p>"By providing an easy way to power this multiplayer experience, we were able to focus
+on making the gameplay come alive &mdash; like the stunts, which are more daring and
+slicker than ever (with more of them to master), or the realistic detail of the water
+splashing against the camera lens.”</p>
+
+</div>
+
+<div style="clear:both;margin-top:40px;width:auto;">
+
+ <img src="{@docRoot}images/distribute/vector-unit-rt-gpgames.jpg"></a>
+
+ <div style="width:600px;margin-top:0px;padding:0 90px;">
+ <p class="image-caption"><span style="font-weight:500;">Multiplayer and more:</span>
+ Google Play game services helped Vector Unit pack an awesome multiplayer experience
+ into Riptide GP 2, so they could focus on building a great gaming experience.</p>
+ </div>
+</div>
+</div> <!-- END STORY -->
+
+
+
diff --git a/docs/html/distribute/googleplay/spotlight/index.jd b/docs/html/distribute/googleplay/spotlight/index.jd
index 7004b0a63cb4..c59962892cae 100644
--- a/docs/html/distribute/googleplay/spotlight/index.jd
+++ b/docs/html/distribute/googleplay/spotlight/index.jd
@@ -3,14 +3,7 @@ walkthru=0
header.hide=0
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
+
<p>Android developers, their apps, and their successes with Android and Google Play. </p>
diff --git a/docs/html/distribute/index.jd b/docs/html/distribute/index.jd
index 8e7c6e1b221b..54f93015e8d6 100644
--- a/docs/html/distribute/index.jd
+++ b/docs/html/distribute/index.jd
@@ -2,14 +2,6 @@ page.title=Distribute Apps
header.hide=1
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
<div class="marquee">
diff --git a/docs/html/distribute/promote/device-art.jd b/docs/html/distribute/promote/device-art.jd
index 09a39417fe46..89231b2bed28 100644
--- a/docs/html/distribute/promote/device-art.jd
+++ b/docs/html/distribute/promote/device-art.jd
@@ -1,13 +1,5 @@
page.title=Device Art Generator
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
<p>The device art generator allows you to quickly wrap your app screenshots in real device artwork.
This provides better visual context for your app screenshots on your web site or in other
@@ -47,7 +39,9 @@ feature image or screenshots for your Google Play app listing.</p>
</p>
</div>
<div class="layout-content-col span-10">
- <div id="output">No input image.</div>
+ <!-- position:relative fixes an issue where dragging an image out of a inline-block container
+ produced no drag feedback image in Chrome 28. -->
+ <div id="output" style="position:relative">No input image.</div>
</div>
</div>
@@ -143,6 +137,8 @@ feature image or screenshots for your Google Play app listing.</p>
// Global variables
var g_currentImage;
var g_currentDevice;
+ var g_currentObjectURL;
+ var g_currentBlob;
// Global constants
var MSG_INVALID_INPUT_IMAGE = 'Invalid screenshot provided. Screenshots must be PNG files '
@@ -267,14 +263,15 @@ feature image or screenshots for your Google Play app listing.</p>
return;
}
+ polyfillCanvasToBlob();
setupUI();
// Set up Chrome drag-out
$.event.props.push("dataTransfer");
document.body.addEventListener('dragstart', function(e) {
- var a = e.target;
- if (a.classList.contains('dragout')) {
- e.dataTransfer.setData('DownloadURL', a.dataset.downloadurl);
+ var target = e.target;
+ if (target.classList.contains('dragout')) {
+ e.dataTransfer.setData('DownloadURL', target.dataset.downloadurl);
}
}, false);
});
@@ -419,7 +416,7 @@ feature image or screenshots for your Google Play app listing.</p>
ctx.translate(-h, 0);
ctx.drawImage(g_currentImage, 0, 0);
- loadImageFromUri(canvas.toDataURL(), function(img) {
+ loadImageFromUri(canvas.toDataURL('image/png'), function(img) {
g_currentImage = img;
createFrame();
});
@@ -459,10 +456,10 @@ feature image or screenshots for your Google Play app listing.</p>
var resourceImages = {};
loadImageResources(resList, function(r) {
resourceImages = r;
- continuation_();
+ continueWithResources_();
});
- function continuation_() {
+ function continueWithResources_() {
var width = resourceImages['back'].naturalWidth;
var height = resourceImages['back'].naturalHeight;
var offset = port ? g_currentDevice.portOffset : g_currentDevice.landOffset;
@@ -486,17 +483,46 @@ feature image or screenshots for your Google Play app listing.</p>
ctx.drawImage(resourceImages['fore'], 0, 0);
}
- var dataUrl = canvas.toDataURL();
+ window.URL = window.URL || window.webkitURL;
+ if (canvas.toBlob && window.URL.createObjectURL) {
+ if (g_currentObjectURL) {
+ window.URL.revokeObjectURL(g_currentObjectURL);
+ g_currentObjectURL = null;
+ }
+ if (g_currentBlob) {
+ if (g_currentBlob.close) {
+ g_currentBlob.close();
+ }
+ g_currentBlob = null;
+ }
+
+ canvas.toBlob(function(blob) {
+ if (!blob) {
+ continueWithFinalUrl_(canvas.toDataURL('image/png'));
+ return;
+ }
+ g_currentBlob = blob;
+ g_currentObjectURL = window.URL.createObjectURL(blob);
+ continueWithFinalUrl_(g_currentObjectURL);
+ }, 'image/png');
+ } else {
+ continueWithFinalUrl_(canvas.toDataURL('image/png'));
+ }
+ }
+
+ function continueWithFinalUrl_(imageUrl) {
var filename = g_currentFilename
- ? ('framed_' + g_currentFilename)
+ ? g_currentFilename.replace(/^(.+?)(\.\w+)?$/, '$1_framed.png')
: 'framed_screenshot.png';
var $link = $('<a>')
.attr('download', filename)
- .attr('href', dataUrl)
- .attr('draggable', true)
- .attr('data-downloadurl', ['image/png', filename, dataUrl].join(':'))
- .append($('<img>').attr('src', dataUrl))
+ .attr('href', imageUrl)
+ .append($('<img>')
+ .addClass('dragout')
+ .attr('src', imageUrl)
+ .attr('draggable', true)
+ .attr('data-downloadurl', ['image/png', filename, imageUrl].join(':')))
.appendTo($('#output').empty());
$('#frame-customizations').show();
@@ -566,14 +592,14 @@ feature image or screenshots for your Google Play app listing.</p>
var file = null;
for (var i = 0; i < fileList.length; i++) {
- if (fileList[i].type.toLowerCase().match(/^image\/png/)) {
+ if (fileList[i].type.toLowerCase().match(/^image\/(png|jpeg|jpg)/)) {
file = fileList[i];
break;
}
}
if (!file) {
- alert('Please use a valid screenshot file (PNG format).');
+ alert('Please use a valid screenshot file (PNG or JPEG format).');
callback(null);
return;
}
@@ -609,4 +635,28 @@ feature image or screenshots for your Google Play app listing.</p>
fileReader.readAsDataURL(file);
}
+
+ /**
+ * Adds a simple version of Canvas.toBlob if toBlob isn't available.
+ */
+ function polyfillCanvasToBlob() {
+ if (!HTMLCanvasElement.prototype.toBlob && window.Blob) {
+ HTMLCanvasElement.prototype.toBlob = function(callback, mimeType, quality) {
+ if (typeof callback != 'function') {
+ throw new TypeError('Function expected');
+ }
+ var dataURL = this.toDataURL(mimeType, quality);
+ mimeType = dataURL.split(';')[0].split(':')[1];
+ var bs = window.atob(dataURL.split(',')[1]);
+ if (dataURL == 'data:,' || !bs.length) {
+ callback(null);
+ return;
+ }
+ for (var ui8arr = new Uint8Array(bs.length), i = 0; i < bs.length; ++i) {
+ ui8arr[i] = bs.charCodeAt(i);
+ }
+ callback(new Blob([ui8arr.buffer /* req'd for Safari */ || ui8arr], {type: mimeType}));
+ };
+ }
+ }
</script>
diff --git a/docs/html/google/index.jd b/docs/html/google/index.jd
index 095388e0dc6e..4020cf451274 100644
--- a/docs/html/google/index.jd
+++ b/docs/html/google/index.jd
@@ -1,14 +1,6 @@
page.title=Google Services
header.hide=1
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
<style>
div.landing-cell,
diff --git a/docs/html/google/play-services/auth.jd b/docs/html/google/play-services/auth.jd
index 3ccc81a8240b..7acaf1cd6834 100644
--- a/docs/html/google/play-services/auth.jd
+++ b/docs/html/google/play-services/auth.jd
@@ -23,7 +23,7 @@ page.tags="AccountManager","oauth2"
</p>
<p>For implementation details, see the sample in <code>&lt;android-sdk&gt;/extras/google-play-services/samples/auth</code>,
-which shows you how to carry out these basic steps for obtaining an acesss token.</p>
+which shows you how to carry out these basic steps for obtaining an access token.</p>
<h2 id="choose">Choosing an Account</h2>
<p>
diff --git a/docs/html/google/play/billing/gp-purchase-status-api.jd b/docs/html/google/play/billing/gp-purchase-status-api.jd
index d6b251ea9d0a..c5b84616d0aa 100644
--- a/docs/html/google/play/billing/gp-purchase-status-api.jd
+++ b/docs/html/google/play/billing/gp-purchase-status-api.jd
@@ -118,13 +118,13 @@ Purchase Status API. For security reasons, the app should not normally attempt t
the purchase itself using the Purchase Status API.</p>
<p>If the backend server determines that the purchase is valid, it notifies the
-app and grant access to the content. For improved performance, the backend servers
-should store the purchase details and order status in a local database, updated a
+app and grants access to the content. For improved performance, the backend servers
+should store the purchase details and order status in a local database, updated at
intervals or as-needed.</p>
-<p>Keep in mind that users will want to be able to use your app at any time, including
+<p>Keep in mind that users will want the ability to use your app at any time, including
when there may be no network connection available. Make sure that your approach to
-purchase verification takes account of the offline use-case.</p>
+purchase verification accounts for the offline use-case.</p>
<h2 id="practices">Using the API Efficiently</h2>
@@ -154,7 +154,7 @@ minimize your access using the techniques below. </p>
query each day to check the status of expiring subscriptions, then update the database.
Note that:
<ul>
- <li>Your servers should not query all subscriptions every day</li>
+ <li>Your servers should not query all subscriptions every day.</li>
<li>Your servers should never query subscription status dynamically, based on
individual requests from your Android application.</li>
</ul>
@@ -162,5 +162,6 @@ minimize your access using the techniques below. </p>
</ul>
<p>By following those general guidelines, your implementation will offer the
-best possible performance for users and minimize use of the Google Play Android
-Developer API.</p>
+best possible performance for users and minimize use of the <a
+href="https://developers.google.com/android-publisher/v1_1/">Google Play Android
+Developer API</a>.</p>
diff --git a/docs/html/google/play/billing/index.jd b/docs/html/google/play/billing/index.jd
index 0818514fd532..481a79ce0143 100644
--- a/docs/html/google/play/billing/index.jd
+++ b/docs/html/google/play/billing/index.jd
@@ -1,13 +1,5 @@
page.title=Google Play In-app Billing
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
<p>In-app Billing is a Google Play service that lets you sell digital content from inside
your applications. You can use the service to sell a wide range of content, including downloadable
diff --git a/docs/html/guide/components/index.jd b/docs/html/guide/components/index.jd
index 6ede87350fb9..87bae53bf22a 100644
--- a/docs/html/guide/components/index.jd
+++ b/docs/html/guide/components/index.jd
@@ -4,14 +4,6 @@ page.landing.intro=Android's application framework lets you create extremely ric
page.landing.image=images/develop/app_components.png
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
<div class="landing-docs">
diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs
index db08c3e62645..2a313748744a 100644
--- a/docs/html/guide/guide_toc.cs
+++ b/docs/html/guide/guide_toc.cs
@@ -146,9 +146,6 @@
<li><a href="<?cs var:toroot ?>guide/topics/ui/layout/grid.html">
<span class="en">Grid Layout</span>
</a></li>
- <li><a href="<?cs var:toroot ?>guide/topics/ui/layout/tabs.html">
- <span class="en">Tab Layout</span>
- </a></li>
-->
<li><a href="<?cs var:toroot ?>guide/topics/ui/layout/listview.html">
<span class="en">List View</span>
diff --git a/docs/html/guide/topics/ui/declaring-layout.jd b/docs/html/guide/topics/ui/declaring-layout.jd
index 6398646e70f1..6586c2f1a767 100644
--- a/docs/html/guide/topics/ui/declaring-layout.jd
+++ b/docs/html/guide/topics/ui/declaring-layout.jd
@@ -359,23 +359,6 @@ partially or totally obscuring them (unless the newer object is transparent).
<p>Enables you to specify the location of child objects relative to each other (child A to
the left of child B) or to the parent (aligned to the top of the parent).</p>
</div>
-
-<!--
-<div class="layout">
- <h4><a href="layout/tabs.html">Tabs</a></h4>
- <a href="layout/tabs.html"><img src="{@docRoot}images/ui/tabs-small.png" alt="" /></a>
- <p>Provides a tab selection list that monitors clicks and enables the application to change
-the screen whenever a tab is clicked.</p>
-</div>
-
-<div class="layout first">
- <h4><a href="layout/grid.html">Table Layout</a></h4>
- <a href="layout/table.html"><img src="{@docRoot}images/ui/gridlayout-small.png" alt="" /></a>
- <p>A tabular layout with an arbitrary number of rows and columns, each cell holding the
-widget of your choice. The rows resize to fit the largest column. The cell borders are not
-visible.</p>
-</div>
--->
<div class="layout">
<h4><a href="{@docRoot}guide/webapps/webview.html">Web View</a></h4>
diff --git a/docs/html/guide/topics/ui/layout/tabs.jd b/docs/html/guide/topics/ui/layout/tabs.jd
deleted file mode 100644
index 62663de8a84d..000000000000
--- a/docs/html/guide/topics/ui/layout/tabs.jd
+++ /dev/null
@@ -1,219 +0,0 @@
-page.title=Tabbed
-parent.title=Layouts
-parent.link=layout-objects.html
-@jd:body
-<div id="qv-wrapper">
-<div id="qv">
-<h2>In this document</h2>
- <ol>
- <li><a href="#example">Example</a></li>
- </ol>
- <h2>Key classes</h2>
- <ol>
-<li>{@link android.widget.TabWidget}</li>
-<li>{@link android.widget.TabHost}</li>
-<li>{@link android.widget.TabHost.TabSpec}</li>
-<li>{@link android.widget.FrameLayout}</li>
- </ol>
-</div>
-</div>
-<p>To create a tabbed UI, you need to use a {@link android.widget.TabHost} and a {@link
-android.widget.TabWidget}. The {@link android.widget.TabHost} must be the root node for the layout,
-which contains both the {@link android.widget.TabWidget} for displaying the tabs and a {@link
-android.widget.FrameLayout} for displaying the tab content.</p>
-
-<img src="{@docRoot}images/ui/tabs.png" alt="" />
-
-<p>You can implement your tab content in one of two ways: use the tabs to swap
-{@link android.view.View}s within the same {@link android.app.Activity}, or use the tabs to change
-between entirely separate activities. Which method you want for your application will depend on your
-demands, but if each tab provides a distinct user activity, then it probably makes sense to use
-a separate {@link android.app.Activity} for each tab, so that you can better manage the application
-in discrete groups, rather than one massive application and layout.</p>
-<h2 id="example">Example</h2>
-<p>In this tutorial, you'll create a tabbed UI that uses a separate {@link
-android.app.Activity} for each tab.</p>
-
-<ol>
- <li>Start a new project named <em>HelloTabWidget</em>.</li>
- <li>First, create three separate {@link android.app.Activity} classes in your project:
-<code>ArtistsActivity</code>, <code>AlbumsActivity</code>, and <code>SongsActivity</code>. These
-will each represent a separate tab. For now, make each one display a simple message using a {@link
-android.widget.TextView}. For example:
-<pre>
-public class ArtistsActivity extends Activity {
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- TextView textview = new TextView(this);
- textview.setText("This is the Artists tab");
- setContentView(textview);
- }
-}
-</pre>
- <p>Notice that this doesn't use a layout file. Just create a {@link
-android.widget.TextView}, give it some text and set that as the content. Duplicate this for
-each of the three activities, and add the corresponding <code>&lt;activity/&gt;</code> tags to the Android Manifest file.</p>
-
- <li>You need an icon for each of your tabs. For each icon, you should create two versions: one
-for when the tab is selected and one for when it is unselected. The
-general design recommendation is for the selected icon to be a dark color (grey), and the
-unselected icon to be a light color (white). (See the <a
-href="{@docRoot}guide/practices/ui_guidelines/icon_design.html#tabstructure">Icon Design
-Guidelines</a>.) For example:
- <p>
- <img src="images/ic_tab_artists_white.png" title="unselected tab icon" alt="" />
- <img src="images/ic_tab_artists_grey.png" title="selected tab icon" alt="" />
- </p>
- <p>For this tutorial, you can copy these images and use them for all three tabs. (When you
-create tabs in your own application, you should create customized tab icons.)</p>
- <p>Now create a <a
-href="{@docRoot}guide/topics/resources/drawable-resource.html#StateList">state-list drawable</a>
-that specifies which image to use for each tab state:</p>
- <ol>
- <li>Save the icon images in your project <code>res/drawable/</code> directory.</li>
- <li>Create a new XML file in <code>res/drawable/</code>
-named <code>ic_tab_artists.xml</code> and insert the following:
-<pre>
-&lt;?xml version="1.0" encoding="utf-8"?>
-&lt;selector xmlns:android="http://schemas.android.com/apk/res/android">
- &lt;!-- When selected, use grey -->
- &lt;item android:drawable="@drawable/ic_tab_artists_grey"
- android:state_selected="true" />
- &lt;!-- When not selected, use white-->
- &lt;item android:drawable="@drawable/ic_tab_artists_white" />
-&lt;/selector>
-</pre>
- <p>This is a <a
-href="{@docRoot}guide/topics/resources/drawable-resource.html#StateList">state-list drawable</a>,
-which you will apply as the tab image. When the tab state changes, the tab icon will
-automatically switch between the images defined here.</p>
- </li>
- </ol>
- </li>
-
- <li>Open the <code>res/layout/main.xml</code> file and insert the following:
- <pre>
-&lt;?xml version="1.0" encoding="utf-8"?>
-&lt;TabHost xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@android:id/tabhost"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- &lt;LinearLayout
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:padding="5dp">
- &lt;TabWidget
- android:id="@android:id/tabs"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content" />
- &lt;FrameLayout
- android:id="@android:id/tabcontent"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:padding="5dp" />
- &lt;/LinearLayout>
-&lt;/TabHost>
-</pre>
- <p>This is the layout that will display the tabs and provide navigation between each {@link
- android.app.Activity} created above.</p>
- <p>The {@link android.widget.TabHost} requires that a {@link android.widget.TabWidget} and a
-{@link android.widget.FrameLayout} both live somewhere within it. To position the {@link
-android.widget.TabWidget} and {@link android.widget.FrameLayout} vertically, a {@link
-android.widget.LinearLayout} is used. The {@link android.widget.FrameLayout} is where the content
-for each tab goes, which is empty now because the {@link android.widget.TabHost} will automatically
-embed each {@link android.app.Activity} within it.</p>
- <p>Notice that the {@link android.widget.TabWidget} and the {@link android.widget.FrameLayout}
- elements have the IDs {@code tabs} and {@code tabcontent}, respectively. These names
- must be used so that the {@link android.widget.TabHost} can retrieve references to each of
- them. It expects exactly these names.</p>
- </li>
-
- <li>Now open <code>HelloTabWidget.java</code> and make it extend {@link
- android.app.TabActivity}:</p>
-<pre>
-public class HelloTabWidget extends TabActivity {
-</pre>
- </li>
- <li>Use the following code for the {@link android.app.Activity#onCreate(Bundle) onCreate()}
- method:
-<pre>
-public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
-
- Resources res = getResources(); // Resource object to get Drawables
- TabHost tabHost = getTabHost(); // The activity TabHost
- TabHost.TabSpec spec; // Resusable TabSpec for each tab
- Intent intent; // Reusable Intent for each tab
-
- // Create an Intent to launch an Activity for the tab (to be reused)
- intent = new Intent().setClass(this, ArtistsActivity.class);
-
- // Initialize a TabSpec for each tab and add it to the TabHost
- spec = tabHost.newTabSpec("artists").setIndicator("Artists",
- res.getDrawable(R.drawable.ic_tab_artists))
- .setContent(intent);
- tabHost.addTab(spec);
-
- // Do the same for the other tabs
- intent = new Intent().setClass(this, AlbumsActivity.class);
- spec = tabHost.newTabSpec("albums").setIndicator("Albums",
- res.getDrawable(R.drawable.ic_tab_albums))
- .setContent(intent);
- tabHost.addTab(spec);
-
- intent = new Intent().setClass(this, SongsActivity.class);
- spec = tabHost.newTabSpec("songs").setIndicator("Songs",
- res.getDrawable(R.drawable.ic_tab_songs))
- .setContent(intent);
- tabHost.addTab(spec);
-
- tabHost.setCurrentTab(2);
-}
-</pre>
- <p>This sets up each tab with their text and icon, and assigns each one an {@link
-android.app.Activity}.</p>
- <p>A reference to the {@link android.widget.TabHost} is first captured with {@link
-android.app.TabActivity#getTabHost()}. Then, for
-each tab, a {@link android.widget.TabHost.TabSpec} is created to define the tab properties. The
-{@link android.widget.TabHost#newTabSpec(String)} method creates a new {@link
-android.widget.TabHost.TabSpec} identified by the given string tag. For each
-{@link android.widget.TabHost.TabSpec}, {@link
-android.widget.TabHost.TabSpec#setIndicator(CharSequence,Drawable)} is called to set the text and
-icon for the tab, and {@link android.widget.TabHost.TabSpec#setContent(Intent)} is called to specify
-the {@link android.content.Intent} to open the appropriate {@link android.app.Activity}. Each
-{@link android.widget.TabHost.TabSpec} is then added to the {@link android.widget.TabHost} by
-calling {@link android.widget.TabHost#addTab(TabHost.TabSpec)}.</p>
-
- <p>At the very end, {@link
- android.widget.TabHost#setCurrentTab(int)} opens the tab to be displayed by default, specified
- by the index position of the tab.</p>
-
- <p>Notice that not once was the {@link android.widget.TabWidget} object referenced. This is
- because a {@link android.widget.TabWidget} must always be a child of a {@link
- android.widget.TabHost}, which is what you use for almost all interaction with the tabs. So when
- a tab is added to the {@link android.widget.TabHost}, it's automatically added to the child
- {@link android.widget.TabWidget}.</p>
- </li>
-
- <li>Now open the Android Manifest file and add the <code>NoTitleBar</code> theme to the
-<em>HelloTabWidget</em>'s
- <code>&lt;activity></code> tag. This will remove the default application title from the top
- of the layout, leaving more space for the tabs, which effectively operate as their own titles.
- The <code>&lt;activity></code> tag should look like this:
-<pre>
-&lt;activity android:name=".HelloTabWidget" android:label="@string/app_name"
- android:theme="&#64;android:style/Theme.NoTitleBar">
-</pre>
- </li>
-
- <li>Run the application.</li>
-</ol>
-
-
-<p>Your application should look like this (though your icons may be different):</p>
-<img src="images/hello-tabwidget.png" width="150px" />
-
-
diff --git a/docs/html/images/distribute/concrete-pbc-gpgames.jpg b/docs/html/images/distribute/concrete-pbc-gpgames.jpg
new file mode 100644
index 000000000000..c97ba8e74296
--- /dev/null
+++ b/docs/html/images/distribute/concrete-pbc-gpgames.jpg
Binary files differ
diff --git a/docs/html/images/distribute/glu-ew-gpgames.jpg b/docs/html/images/distribute/glu-ew-gpgames.jpg
new file mode 100644
index 000000000000..8d97f8de71c1
--- /dev/null
+++ b/docs/html/images/distribute/glu-ew-gpgames.jpg
Binary files differ
diff --git a/docs/html/images/distribute/vector-unit-rt-gpgames.jpg b/docs/html/images/distribute/vector-unit-rt-gpgames.jpg
new file mode 100644
index 000000000000..323f188aaa3c
--- /dev/null
+++ b/docs/html/images/distribute/vector-unit-rt-gpgames.jpg
Binary files differ
diff --git a/docs/html/index.jd b/docs/html/index.jd
index a945f0a7a504..bbb6a16bc567 100644
--- a/docs/html/index.jd
+++ b/docs/html/index.jd
@@ -3,14 +3,6 @@ no_footer_links=true
carousel=true
page.metaDescription=The official site for Android developers. Provides the Android SDK and documentation for app developers and designers.
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
<div class="wrap">
diff --git a/docs/html/sdk/index.jd b/docs/html/sdk/index.jd
index aa3b2ec8761b..4ea375263d7d 100644
--- a/docs/html/sdk/index.jd
+++ b/docs/html/sdk/index.jd
@@ -5,43 +5,43 @@ header.hide=1
page.metaDescription=Download the official Android SDK to develop apps for Android-powered devices.
-sdk.linux32_bundle_download=adt-bundle-linux-x86-20130717.zip
-sdk.linux32_bundle_bytes=440035305
-sdk.linux32_bundle_checksum=ecfacb91df1ee63cce1edd4f1a5cda5a
+sdk.linux32_bundle_download=adt-bundle-linux-x86-20130729.zip
+sdk.linux32_bundle_bytes=457716139
+sdk.linux32_bundle_checksum=b3686d10dc1cbceba1074404d4386283
-sdk.linux64_bundle_download=adt-bundle-linux-x86_64-20130717.zip
-sdk.linux64_bundle_bytes=440322117
-sdk.linux64_bundle_checksum=ab177a06784340b8f1d136651e3dc62a
+sdk.linux64_bundle_download=adt-bundle-linux-x86_64-20130729.zip
+sdk.linux64_bundle_bytes=458006784
+sdk.linux64_bundle_checksum=1fabcc3f772ba8b2fc194d6e0449da17
-sdk.mac64_bundle_download=adt-bundle-mac-x86_64-20130717.zip
-sdk.mac64_bundle_bytes=411609229
-sdk.mac64_bundle_checksum=07c891212a49b5f8495ea9d8d47ba3fe
+sdk.mac64_bundle_download=adt-bundle-mac-x86_64-20130729.zip
+sdk.mac64_bundle_bytes=428792424
+sdk.mac64_bundle_checksum=6c42b9966abcfa8a75c0ee83d0d95882
-sdk.win32_bundle_download=adt-bundle-windows-x86-20130717.zip
-sdk.win32_bundle_bytes=446783216
-sdk.win32_bundle_checksum=0dd91095999d3539ca1ec4033d83d935
+sdk.win32_bundle_download=adt-bundle-windows-x86-20130729.zip
+sdk.win32_bundle_bytes=463931746
+sdk.win32_bundle_checksum=51faf4e5fdf9c5b4a176179a99ce3511
-sdk.win64_bundle_download=adt-bundle-windows-x86_64-20130717.zip
-sdk.win64_bundle_bytes=446911629
-sdk.win64_bundle_checksum=61ec74995b39166db7f079017a028cec
+sdk.win64_bundle_download=adt-bundle-windows-x86_64-20130729.zip
+sdk.win64_bundle_bytes=464064756
+sdk.win64_bundle_checksum=e8f05c1fddb8e609e880de23113c7426
-sdk.linux_download=android-sdk_r22.0.4-linux.tgz
-sdk.linux_bytes=105640988
-sdk.linux_checksum=4a5db98a58c68c24e66f04f07ac77da5
+sdk.linux_download=android-sdk_r22.0.5-linux.tgz
+sdk.linux_bytes=105641005
+sdk.linux_checksum=8201b10c21510f082c54f58a9bb082c8
-sdk.mac_download=android-sdk_r22.0.4-macosx.zip
-sdk.mac_bytes=77225662
-sdk.mac_checksum=384752505f4f2ba3627bd6aad0697f11
+sdk.mac_download=android-sdk_r22.0.5-macosx.zip
+sdk.mac_bytes=77225724
+sdk.mac_checksum=94f3cbe896c332b94ee0408ae610a4b8
-sdk.win_download=android-sdk_r22.0.4-windows.zip
-sdk.win_bytes=113507679
-sdk.win_checksum=320b11d1ed85fd3f5e937697c333d895
+sdk.win_download=android-sdk_r22.0.5-windows.zip
+sdk.win_bytes=113510621
+sdk.win_checksum=30695dffc41e0d7cf9ff948ab0c48920
-sdk.win_installer=installer_r22.0.4-windows.exe
-sdk.win_installer_bytes=93502726
-sdk.win_installer_checksum=96a8ae367d84ed219e1eb2cf473667d0
+sdk.win_installer=installer_r22.0.5-windows.exe
+sdk.win_installer_bytes=93505782
+sdk.win_installer_checksum=940849be19ac6151e3e35c8706c81d86
diff --git a/docs/html/sdk/installing/installing-adt.jd b/docs/html/sdk/installing/installing-adt.jd
index 2a09636a256f..bdc07d02283d 100644
--- a/docs/html/sdk/installing/installing-adt.jd
+++ b/docs/html/sdk/installing/installing-adt.jd
@@ -1,8 +1,8 @@
page.title=Installing the Eclipse Plugin
-adt.zip.version=22.0.4
-adt.zip.download=ADT-22.0.4.zip
-adt.zip.bytes=16838756
-adt.zip.checksum=f0291f4bb9d78ec34a7751cd2402cc2a
+adt.zip.version=22.0.5
+adt.zip.download=ADT-22.0.5.zip
+adt.zip.bytes=16839757
+adt.zip.checksum=1097fccf32063e3638a9d27aa0f295ca
@jd:body
diff --git a/docs/html/tools/extras/oem-usb.jd b/docs/html/tools/extras/oem-usb.jd
index 5e0e893710b0..e0bbf335d89b 100644
--- a/docs/html/tools/extras/oem-usb.jd
+++ b/docs/html/tools/extras/oem-usb.jd
@@ -227,7 +227,7 @@ href="http://www.acer.com/worldwide/support/mobile.html">http://www.acer.com/wor
<tr>
<td style="font-variant:small-caps">alcatel one touch</td>
<td><a
-href="http://www.alcatel-mobilephones.com/global/Android-Downloads">http://www.alcatel-mobilephones.com/global/Android-Downloads</a></td>
+href="http://www.alcatelonetouch.com/global-en/support/faq/usbdriver.html">http://www.alcatelonetouch.com/global-en/support/faq/usbdriver.html</a></td>
</tr>
<tr>
<td>Asus</td>
diff --git a/docs/html/tools/index.jd b/docs/html/tools/index.jd
index e9094a79b820..f9d452c0c253 100644
--- a/docs/html/tools/index.jd
+++ b/docs/html/tools/index.jd
@@ -1,13 +1,6 @@
page.title=Developer Tools
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
+
<img src="{@docRoot}images/tools-home.png" style="float:right;" height="415" width="763" />
diff --git a/docs/html/tools/sdk/eclipse-adt.jd b/docs/html/tools/sdk/eclipse-adt.jd
index 7b0b5a8f0eab..e9c514e064d3 100644
--- a/docs/html/tools/sdk/eclipse-adt.jd
+++ b/docs/html/tools/sdk/eclipse-adt.jd
@@ -57,6 +57,41 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<div class="toggle-content opened">
<p><a href="#" onclick="return toggleContent(this)">
<img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-content-img"
+ alt=""/>ADT 22.0.5</a> <em>(July 2013)</em>
+ </p>
+
+ <div class="toggle-content-toggleme">
+<dl>
+ <dt>Dependencies:</dt>
+
+ <dd>
+ <ul>
+ <li>Java 1.6 or higher is required.</li>
+ <li>Eclipse Helios (Version 3.6.2) or higher is required.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r22.0.5</a>.
+ If you haven't already installed SDK Tools r22.0.5 into your SDK, use the
+ Android SDK Manager to do so.</li>
+ </ul>
+ </dd>
+
+ <dt>General Notes:</dt>
+ <dd>
+ <ul>
+ <li>Fixed Renderscript compilation issue for Windows platforms.</li>
+ <li>Updated <a href="{@docRoot}tools/help/systrace.html">Systrace</a> report generation
+ in the Monitor and DDMS perspectives.</li>
+ </ul>
+ </dd>
+
+</dl>
+</div>
+</div>
+
+
+<div class="toggle-content closed">
+ <p><a href="#" onclick="return toggleContent(this)">
+ <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img"
alt=""/>ADT 22.0.4</a> <em>(July 2013)</em>
</p>
@@ -66,11 +101,12 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<dd>
<ul>
- <li>Java 1.6 or higher is required for ADT 22.0.4.</li>
- <li>Eclipse Helios (Version 3.6.2) or higher is required for ADT 22.0.4.</li>
- <li>ADT 22.0.4 is designed for use with <a href="{@docRoot}tools/sdk/tools-notes.html">SDK
- Tools r22.0.4</a>. If you haven't already installed SDK Tools r22.0.4 into your SDK, use the
- Android SDK Manager to do so.</li>
+ <li>Java 1.6 or higher is required.</li>
+ <li>Eclipse Helios (Version 3.6.2) or higher is required.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r22.0.4</a>.
+ If you haven't already installed SDK Tools r22.0.4 into your SDK, use the
+ Android SDK Manager to do so.</li>
</ul>
</dd>
@@ -102,11 +138,12 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<dd>
<ul>
- <li>Java 1.6 or higher is required for ADT 22.0.1.</li>
- <li>Eclipse Helios (Version 3.6.2) or higher is required for ADT 22.0.1.</li>
- <li>ADT 22.0.1 is designed for use with <a href="{@docRoot}tools/sdk/tools-notes.html">SDK
- Tools r22.0.1</a>. If you haven't already installed SDK Tools r22 into your SDK, use the
- Android SDK Manager to do so.</li>
+ <li>Java 1.6 or higher is required.</li>
+ <li>Eclipse Helios (Version 3.6.2) or higher is required.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r22.0.1</a>.
+ If you haven't already installed SDK Tools r22.0.1 into your SDK, use the
+ Android SDK Manager to do so.</li>
</ul>
</dd>
@@ -138,11 +175,12 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<dd>
<ul>
- <li>Java 1.6 or higher is required for ADT 22.0.0.</li>
- <li>Eclipse Helios (Version 3.6.2) or higher is required for ADT 22.0.0.</li>
- <li>ADT 22.0.0 is designed for use with <a href="{@docRoot}tools/sdk/tools-notes.html">SDK
- Tools r22</a>. If you haven't already installed SDK Tools r22 into your SDK, use the
- Android SDK Manager to do so.</li>
+ <li>Java 1.6 or higher is required.</li>
+ <li>Eclipse Helios (Version 3.6.2) or higher is required.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r22</a>.
+ If you haven't already installed SDK Tools r22 into your SDK, use the
+ Android SDK Manager to do so.</li>
</ul>
</dd>
@@ -181,11 +219,12 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<dd>
<ul>
- <li>Java 1.6 or higher is required for ADT 21.1.0.</li>
- <li>Eclipse Helios (Version 3.6.2) or higher is required for ADT 21.1.0.</li>
- <li>ADT 21.1.0 is designed for use with <a href="{@docRoot}tools/sdk/tools-notes.html">SDK
- Tools r21.1</a>. If you haven't already installed SDK Tools r21.1 into your SDK, use the
- Android SDK Manager to do so.</li>
+ <li>Java 1.6 or higher is required.</li>
+ <li>Eclipse Helios (Version 3.6.2) or higher is required.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r21.1</a>.
+ If you haven't already installed SDK Tools r21.1 into your SDK, use the
+ Android SDK Manager to do so.</li>
</ul>
</dd>
@@ -238,11 +277,12 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<dd>
<ul>
- <li>Java 1.6 or higher is required for ADT 21.0.1.</li>
- <li>Eclipse Helios (Version 3.6.2) or higher is required for ADT 21.0.1.</li>
- <li>ADT 21.0.1 is designed for use with <a href="{@docRoot}tools/sdk/tools-notes.html">SDK
- Tools r21.0.1</a>. If you haven't already installed SDK Tools r21.0.1 into your SDK, use the
- Android SDK Manager to do so.</li>
+ <li>Java 1.6 or higher is required.</li>
+ <li>Eclipse Helios (Version 3.6.2) or higher is required.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r21.0.1</a>.
+ If you haven't already installed SDK Tools r21.0.1 into your SDK, use the
+ Android SDK Manager to do so.</li>
</ul>
</dd>
@@ -357,11 +397,12 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<dd>
<ul>
- <li>Java 1.6 or higher is required for ADT 20.0.3.</li>
- <li>Eclipse Helios (Version 3.6.2) or higher is required for ADT 21.0.0.</li>
- <li>ADT 21.0.0 is designed for use with <a href="{@docRoot}tools/sdk/tools-notes.html">SDK
- Tools r21</a>. If you haven't already installed SDK Tools r21.0.0 into your SDK, use the
- Android SDK Manager to do so.</li>
+ <li>Java 1.6 or higher is required.</li>
+ <li>Eclipse Helios (Version 3.6.2) or higher is required.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r21</a>.
+ If you haven't already installed SDK Tools r21.0.0 into your SDK, use the
+ Android SDK Manager to do so.</li>
</ul>
</dd>
@@ -478,11 +519,12 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<dd>
<ul>
- <li>Java 1.6 or higher is required for ADT 20.0.3.</li>
- <li>Eclipse Helios (Version 3.6.2) or higher is required for ADT 20.0.3.</li>
- <li>ADT 20.0.3 is designed for use with <a href="{@docRoot}tools/sdk/tools-notes.html">SDK
- Tools r20.0.3</a>. If you haven't already installed SDK Tools r20.0.3 into your SDK, use the
- Android SDK Manager to do so.</li>
+ <li>Java 1.6 or higher is required.</li>
+ <li>Eclipse Helios (Version 3.6.2) or higher is required.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r20.0.3</a>.
+ If you haven't already installed SDK Tools r20.0.3 into your SDK, use the
+ Android SDK Manager to do so.</li>
</ul>
</dd>
@@ -512,11 +554,12 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<dd>
<ul>
- <li>Java 1.6 or higher is required for ADT 20.0.2.</li>
- <li>Eclipse Helios (Version 3.6.2) or higher is required for ADT 20.0.2.</li>
- <li>ADT 20.0.2 is designed for use with <a href="{@docRoot}tools/sdk/tools-notes.html">SDK
- Tools r20.0.1</a>. If you haven't already installed SDK Tools r20.0.1 into your SDK, use the
- Android SDK Manager to do so.</li>
+ <li>Java 1.6 or higher is required.</li>
+ <li>Eclipse Helios (Version 3.6.2) or higher is required.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r20.0.1</a>.
+ If you haven't already installed SDK Tools r20.0.1 into your SDK, use the
+ Android SDK Manager to do so.</li>
</ul>
</dd>
@@ -547,11 +590,12 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<dd>
<ul>
- <li>Java 1.6 or higher is required for ADT 20.0.1.</li>
- <li>Eclipse Helios (Version 3.6.2) or higher is required for ADT 20.0.1.</li>
- <li>ADT 20.0.1 is designed for use with <a href="{@docRoot}tools/sdk/tools-notes.html">SDK
- Tools r20.0.1</a>. If you haven't already installed SDK Tools r20.0.1 into your SDK, use the
- Android SDK Manager to do so.</li>
+ <li>Java 1.6 or higher is required.</li>
+ <li>Eclipse Helios (Version 3.6.2) or higher is required.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r20.0.1</a>.
+ If you haven't already installed SDK Tools r20.0.1 into your SDK, use the
+ Android SDK Manager to do so.</li>
</ul>
</dd>
@@ -588,11 +632,12 @@ unprotected receivers for default Android actions.</li>
<dd>
<ul>
- <li>Java 1.6 or higher is required for ADT 20.0.0.</li>
- <li>Eclipse Helios (Version 3.6.2) or higher is required for ADT 20.0.0.</li>
- <li>ADT 20.0.0 is designed for use with <a href="{@docRoot}sdk/tools-notes.html">SDK Tools
- r20</a>. If you haven't already installed SDK Tools r20 into your SDK, use the Android SDK
- Manager to do so.</li>
+ <li>Java 1.6 or higher is required.</li>
+ <li>Eclipse Helios (Version 3.6.2) or higher is required.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}sdk/tools-notes.html">SDK Tools r20</a>.
+ If you haven't already installed SDK Tools r20 into your SDK, use the Android SDK
+ Manager to do so.</li>
</ul>
</dd>
@@ -711,11 +756,12 @@ functions.</li>
<dd>
<ul>
- <li>Java 1.6 or higher is required for ADT 18.0.0.</li>
- <li>Eclipse Helios (Version 3.6.2) or higher is required for ADT 18.0.0.</li>
- <li>ADT 18.0.0 is designed for use with <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools
- r18</a>. If you haven't already installed SDK Tools r18 into your SDK, use the Android SDK
- Manager to do so.</li>
+ <li>Java 1.6 or higher is required.</li>
+ <li>Eclipse Helios (Version 3.6.2) or higher is required.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r18</a>.
+ If you haven't already installed SDK Tools r18 into your SDK, use the Android SDK
+ Manager to do so.</li>
</ul>
</dd>
@@ -746,11 +792,12 @@ functions.</li>
<dd>
<ul>
- <li>Java 1.6 or higher is required for ADT 17.0.0.</li>
- <li>Eclipse Helios (Version 3.6.2) or higher is required for ADT 17.0.0.</li>
- <li>ADT 17.0.0 is designed for use with <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools
- r17</a>. If you haven't already installed SDK Tools r17 into your SDK, use the Android SDK
- Manager to do so.</li>
+ <li>Java 1.6 or higher is required.</li>
+ <li>Eclipse Helios (Version 3.6.2) or higher is required.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r17</a>.
+ If you haven't already installed SDK Tools r17 into your SDK, use the Android SDK
+ Manager to do so.</li>
</ul>
</dd>
@@ -848,9 +895,10 @@ select. (<a href="http://code.google.com/p/android/issues/detail?id=20589">Issue
<dd>
<ul>
- <li>Eclipse Helios (Version 3.6) or higher is required for ADT 16.0.1.</li>
- <li>ADT 16.0.1 is designed for use with <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools
- r16</a>. If you haven't already installed SDK Tools r16 into your SDK, use the Android SDK
+ <li>Eclipse Helios (Version 3.6) or higher is required.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r16</a>.
+ If you haven't already installed SDK Tools r16 into your SDK, use the Android SDK
Manager to do so.</li>
</ul>
</dd>
@@ -882,11 +930,11 @@ select. (<a href="http://code.google.com/p/android/issues/detail?id=20589">Issue
<dd>
<ul>
- <li>Eclipse Helios (Version 3.6) or higher is required for ADT
-16.0.0.</li>
- <li>ADT 16.0.0 is designed for use with <a
-href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r16</a>. If you haven't already installed SDK Tools
-r16 into your SDK, use the Android SDK Manager to do so.</li>
+ <li>Eclipse Helios (Version 3.6) or higher is required for ADT 16.0.0.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r16</a>.
+ If you haven't already installed SDK Tools r16 into your SDK, use
+ the Android SDK Manager to do so.</li>
</ul>
</dd>
@@ -913,9 +961,10 @@ href="http://tools.android.com/recent/lint">more info</a>)</li>
<dl>
<dt>Dependencies:</dt>
- <dd>ADT 15.0.1 is designed for use with <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r15</a>.
- If you haven't already installed SDK Tools r15 into your SDK, use the Android SDK Manager to
- do so.</dd>
+ <dd>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r15</a>.
+ If you haven't already installed SDK Tools r15 into your SDK, use the Android SDK Manager to
+ do so.</dd>
<dt>Bug fixes:</dt>
<dd>
@@ -948,9 +997,10 @@ href="http://tools.android.com/recent/lint">more info</a>)</li>
<dt>Dependencies:</dt>
-<dd>ADT 15.0.0 is designed for use with <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r15</a>.
-If you haven't already installed SDK Tools r15 into your SDK, use the Android SDK Manager to
-do so.</dd>
+<dd>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r15</a>.
+ If you haven't already installed SDK Tools r15 into your SDK, use the Android SDK Manager to
+ do so.</dd>
<dt>Bug fixes:</dt>
<dd>
@@ -980,9 +1030,10 @@ do so.</dd>
<dt>Dependencies:</dt>
-<dd>ADT 14.0.0 is designed for use with <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r14</a>.
-If you haven't already installed SDK Tools r14 into your SDK, use the Android SDK Manager to
-do so.</dd>
+<dd>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r14</a>.
+ If you haven't already installed SDK Tools r14 into your SDK, use the Android SDK Manager to
+ do so.</dd>
<dt>Build system:</dt>
<dd>
@@ -1099,7 +1150,8 @@ Linux</a>.</dd>
<dt>Dependencies:</dt>
-<dd>ADT 12.0.0 is designed for use with <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r12</a>. If you haven't
+<dd>This version of ADT is designed for use with
+<a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r12</a>. If you haven't
already installed SDK Tools r12 into your SDK, use
the Android SDK Manager to do so.</dd>
@@ -1152,7 +1204,7 @@ the Android SDK Manager to do so.</dd>
<dt>Dependencies:</dt>
-<dd>ADT 11.0.0 is designed for use with SDK Tools r11. If you haven't
+<dd>This version of ADT is designed for use with SDK Tools r11. If you haven't
already installed SDK Tools r11 into your SDK, use the Android SDK Manager to do
so.</dd>
@@ -1289,7 +1341,7 @@ href="http://tools.android.com/recent">Android Tools Project Site</a>.</p>
<dt>Dependencies:</dt>
-<dd>ADT 10.0.1 is designed for use with SDK Tools r10. If you haven't
+<dd>This version of ADT is designed for use with SDK Tools r10. If you haven't
already installed SDK Tools r10 into your SDK, use the Android SDK Manager to do
so.</dd>
@@ -1319,7 +1371,7 @@ requires Eclipse 3.5 or higher (as of 10.0.0).</li>
<dt>Dependencies:</dt>
-<dd>ADT 10.0.0 is designed for use with SDK Tools r10. If you haven't
+<dd>This version of ADT is designed for use with SDK Tools r10. If you haven't
already installed SDK Tools r10 into your SDK, use the Android SDK Manager to do
so.</dd>
@@ -1369,7 +1421,7 @@ so.</dd>
<dt>Dependencies:</dt>
-<dd>ADT 9.0.0 is designed for use with SDK Tools r9. If you haven't
+<dd>This version of ADT is designed for use with SDK Tools r9. If you haven't
already installed SDK Tools r9 into your SDK, use the Android SDK Manager to do
so.</dd>
@@ -1478,7 +1530,7 @@ so.</dd>
<dt>Dependencies:</dt>
-<p><p>ADT 8.0.1 is designed for use with SDK Tools r8. If you haven't
+<p>This version of ADT is designed for use with SDK Tools r8. If you haven't
already installed SDK Tools r8 into your SDK, use the Android SDK Manager to do
so.</p></dd>
@@ -1507,7 +1559,7 @@ so.</p></dd>
<dt>Dependencies:</dt>
-<p><p>ADT 8.0.0 is designed for use with SDK Tools r8. If you haven't
+<p>This version of ADT is designed for use with SDK Tools r8. If you haven't
already installed SDK Tools r8 into your SDK, use the Android SDK Manager to do
so.</p></dd>
@@ -1656,7 +1708,7 @@ project support through the Ant build system.</p>
<dl>
<dt>Dependencies:</dt>
-<dd><p>ADT 0.9.6 is designed for use with SDK Tools r5 and later. Before
+<dd><p>This version of ADT is designed for use with SDK Tools r5 and later. Before
updating to ADT 0.9.6, we highly recommend that you use the Android SDK Manager to install SDK
Tools r5 into your SDK.</p></dd>
@@ -1725,7 +1777,7 @@ targets for application launches.</li>
<dl>
<dt>Dependencies:</dt>
-<dd><p>ADT 0.9.5 requires features provided in SDK Tools r4 or higher. If you install
+<dd><p>This version of ADT requires features provided in SDK Tools r4 or higher. If you install
ADT 0.9.5, which is highly recommended, you should use the Android SDK
Manager to download the latest SDK Tools into your SDK. For more information,
see <a href="{@docRoot}sdk/exploring.html">Exploring the SDK</a>.</p>
@@ -1755,7 +1807,7 @@ see <a href="{@docRoot}sdk/exploring.html">Exploring the SDK</a>.</p>
<dl>
<dt>Dependencies:</dt>
-<dd><p>ADT 0.9.4 requires features provided in SDK Tools r3 or higher. If you install
+<dd><p>This version of ADT requires features provided in SDK Tools r3 or higher. If you install
ADT 0.9.4, which is highly recommended, you should use the Android SDK
Manager to download the latest SDK Tools into your SDK. For more information,
see <a href="{@docRoot}sdk/exploring.html">Exploring the SDK</a>.</p>
diff --git a/docs/html/tools/sdk/ndk/index.jd b/docs/html/tools/sdk/ndk/index.jd
index 318764e2b4da..61a91a849389 100644
--- a/docs/html/tools/sdk/ndk/index.jd
+++ b/docs/html/tools/sdk/ndk/index.jd
@@ -63,7 +63,7 @@ page.title=Android NDK
<div id="tos" style="display:none;width:760px;height:0;margin:0 auto">
-<div class="ndk" style="
+<div id="download" class="ndk" style="
z-index: 99;
width: 720px;
position: absolute;
diff --git a/docs/html/tools/sdk/tools-notes.jd b/docs/html/tools/sdk/tools-notes.jd
index cd2d986dade4..4aef8a028505 100644
--- a/docs/html/tools/sdk/tools-notes.jd
+++ b/docs/html/tools/sdk/tools-notes.jd
@@ -26,9 +26,50 @@ Tools you are using, refer to the "Installed Packages" listing in the Android SD
href="http://tools.android.com/knownissues">http://tools.android.com/knownissues</a>.</p>
+
<div class="toggle-content opened">
<p><a href="#" onclick="return toggleContent(this)">
<img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-content-img"
+ alt=""/>SDK Tools, Revision 22.0.5</a> <em>(July 2013)</em>
+ </p>
+
+ <div class="toggle-content-toggleme">
+
+ <dl>
+ <dt>Dependencies:</dt>
+ <dd>
+ <ul>
+ <li>Android SDK Platform-tools revision 16 or later.</li>
+ <li>If you are developing in Eclipse with the
+ <a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a>, note that this version of
+ SDK Tools is designed for use with ADT 22.0.5 and later. If you haven't already, update
+ ADT to 22.0.5.</li>
+ <li>If you are using <a href="{@docRoot}sdk/installing/studio.html">Android Studio</a>,
+ note that this version of the SDK Tools is designed to work with Android Studio
+ 0.2.x and later.</li>
+ <li>If you are developing without an integrated development environment (IDE), you must have
+ <a href="http://ant.apache.org/">Apache Ant</a> 1.8 or later.</li>
+ </ul>
+ </dd>
+
+ <dt>General Notes:</dt>
+ <dd>
+ <ul>
+ <li>Fixed Renderscript compilation issue for Windows platforms with ant.</li>
+ <li>Updated <a href="{@docRoot}tools/help/systrace.html">Systrace</a> to work with the
+ Android 4.3 platform image.</li>
+ <li>Fixed packaging of Renderscript compiler.</li>
+ <li>Build tools 18.0.0 is obsolete and 18.0.1 should be used instead.</li>
+ </ul>
+ </dd>
+ </dl>
+ </div>
+</div>
+
+
+<div class="toggle-content closed">
+ <p><a href="#" onclick="return toggleContent(this)">
+ <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img"
alt=""/>SDK Tools, Revision 22.0.4</a> <em>(July 2013)</em>
</p>
@@ -75,7 +116,7 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<dd>
<ul>
<li>Android SDK Platform-tools revision 16 or later.</li>
- <li>If you are developing in Eclipse with ADT, note that the SDK Tools r22.0.1 is
+ <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is
designed for use with ADT 22.0.1 and later. If you haven't already, update your
<a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 22.0.1.</li>
<li>If you are developing outside Eclipse, you must have
@@ -116,7 +157,7 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<dd>
<ul>
<li>Android SDK Platform-tools revision 16 or later.</li>
- <li>If you are developing in Eclipse with ADT, note that the SDK Tools r22 is
+ <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is
designed for use with ADT 22.0.0 and later. If you haven't already, update your
<a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 22.0.0.</li>
<li>If you are developing outside Eclipse, you must have
@@ -171,7 +212,7 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<dd>
<ul>
<li>Android SDK Platform-tools revision 16 or later.</li>
- <li>If you are developing in Eclipse with ADT, note that the SDK Tools r21.1 is
+ <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is
designed for use with ADT 21.1.0 and later. If you haven't already, update your
<a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 21.1.0.</li>
<li>If you are developing outside Eclipse, you must have
@@ -206,7 +247,7 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<dd>
<ul>
<li>Android SDK Platform-tools revision 16 or later.</li>
- <li>If you are developing in Eclipse with ADT, note that the SDK Tools r21.0.1 is
+ <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is
designed for use with ADT 21.0.1 and later. If you haven't already, update your
<a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 21.0.1.</li>
<li>If you are developing outside Eclipse, you must have
@@ -309,7 +350,7 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<dd>
<ul>
<li>Android SDK Platform-tools revision 16 or later.</li>
- <li>If you are developing in Eclipse with ADT, note that the SDK Tools r21 is designed
+ <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is designed
for use with ADT 21.0.0 and later. If you haven't already, update your
<a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 21.0.0.</li>
<li>If you are developing outside Eclipse, you must have
@@ -395,7 +436,7 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<dd>
<ul>
<li>Android SDK Platform-tools revision 12 or later.</li>
- <li>If you are developing in Eclipse with ADT, note that the SDK Tools r20.0.3 is designed
+ <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is designed
for use with ADT 20.0.3 and later. If you haven't already, update your
<a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 20.0.3.</li>
<li>If you are developing outside Eclipse, you must have
@@ -426,7 +467,7 @@ href="http://tools.android.com/knownissues">http://tools.android.com/knownissues
<dd>
<ul>
<li>Android SDK Platform-tools revision 12 or later.</li>
- <li>If you are developing in Eclipse with ADT, note that the SDK Tools r20.0.1 is designed
+ <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is designed
for use with ADT 20.0.1 and later. If you haven't already, update your
<a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 20.0.1.</li>
<li>If you are developing outside Eclipse, you must have
@@ -462,7 +503,7 @@ unprotected receivers for default Android actions.</li>
<dd>
<ul>
<li>Android SDK Platform-tools revision 12 or later.</li>
- <li>If you are developing in Eclipse with ADT, note that the SDK Tools r20 is designed for
+ <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is designed for
use with ADT 20.0.0 and later. If you haven't already, we highly recommend updating your
<a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 20.0.0.</li>
<li>If you are developing outside Eclipse, you must have
@@ -540,7 +581,7 @@ download and install this update.</p>
<dd>
<ul>
<li>Android SDK Platform-tools revision 9 or later.</li>
- <li>If you are developing in Eclipse with ADT, note that the SDK Tools r19 is designed for
+ <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is designed for
use with ADT 18.0.0 and later. If you haven't already, we highly recommend updating your
<a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 18.0.0.</li>
<li>If you are developing outside Eclipse, you must have
@@ -575,7 +616,7 @@ acceleration.</li>
<dd>
<ul>
<li>Android SDK Platform-tools revision 9 or later.</li>
- <li>If you are developing in Eclipse with ADT, note that the SDK Tools r18 is designed for
+ <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is designed for
use with ADT 18.0.0 and later. If you haven't already, we highly recommend updating your
<a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 18.0.0.</li>
<li>If you are developing outside Eclipse, you must have
@@ -617,7 +658,7 @@ in some cases.</li>
<dd>
<ul>
<li>Android SDK Platform-tools revision 9 or later.</li>
- <li>If you are developing in Eclipse with ADT, note that the SDK Tools r17 is designed for
+ <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is designed for
use with ADT 17.0.0 and later. If you haven't already, we highly recommend updating your
<a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 17.0.0.</li>
<li>If you are developing outside Eclipse, you must have
@@ -721,7 +762,7 @@ ignore attribute. (<a
<dd>
<ul>
<li>Android SDK Platform-tools revision 9 or later.</li>
- <li>If you are developing in Eclipse with ADT, note that the SDK Tools r16 is designed for use
+ <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is designed for use
with ADT 16.0.0 and later. If you haven't already, we highly recommend updating your
<a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 16.0.0.</li>
<li>If you are developing outside Eclipse, you must have <a href="http://ant.apache.org/">Apache
@@ -769,7 +810,7 @@ ignore attribute. (<a
<dt>Dependencies:</dt>
<dd>
<ul><li>Android SDK Platform-tools revision 9 or later.</li>
- <li>If you are developing in Eclipse with ADT, note that the SDK Tools r15 is designed for use
+ <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is designed for use
with ADT 15.0.0 and later. If you haven't already, we highly recommend updating your <a
href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 15.0.0.</li>
<li>If you are developing outside Eclipse, you must have <a href="http://ant.apache.org/">Apache
@@ -817,7 +858,7 @@ ignore attribute. (<a
<dt>Dependencies:</dt>
<dd>
<ul><li>Android SDK Platform-tools revision 8 or later.</li>
- <li>If you are developing in Eclipse with ADT, note that the SDK Tools r14 is designed for use
+ <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is designed for use
with ADT 14.0.0 and later. If you haven't already, we highly recommend updating your <a
href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 14.0.0.</li>
<li>If you are developing outside Eclipse, you must have <a href="http://ant.apache.org/">Apache
@@ -867,7 +908,7 @@ site</a>.</p>
<dl>
<dt>Dependencies:</dt>
<dd>
-<p>If you are developing in Eclipse with ADT, note that the SDK Tools r13 is designed for use with
+<p>If you are developing in Eclipse with ADT, note that this version of SDK Tools is designed for use with
ADT 12.0.0 and later. If you haven't already, we highly recommend updating your <a
href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 12.0.0.</p>
@@ -898,7 +939,7 @@ Ant</a> 1.8 or later.</p>
<dl>
<dt>Dependencies:</dt>
<dd>
-<p>If you are developing in Eclipse with ADT, note that the SDK Tools r12 is designed for use with
+<p>If you are developing in Eclipse with ADT, note that this version of SDK Tools is designed for use with
ADT 12.0.0 and later. If you haven't already, we highly recommend updating your <a
href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 12.0.0.</p>
@@ -926,7 +967,7 @@ Ant</a> 1.8 or later.</p>
<dl>
<dt>Dependencies:</dt>
<dd>
-<p>If you are developing in Eclipse with ADT, note that the SDK Tools r11 is designed for use with
+<p>If you are developing in Eclipse with ADT, note that this version of SDK Tools is designed for use with
ADT 10.0.1 and later. If you haven't already, we highly recommend updating your <a
href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 10.0.1.</p>
@@ -954,7 +995,7 @@ Ant</a> 1.8 or later.</p>
<dl>
<dt>Dependencies:</dt>
<dd>
-<p>If you are developing in Eclipse with ADT, note that the SDK Tools r10 is
+<p>If you are developing in Eclipse with ADT, note that this version of SDK Tools is
designed for use with ADT 10.0.0 and later. After installing SDK Tools r10, we
highly recommend updating your ADT Plugin to 10.0.0.</p>
@@ -985,7 +1026,7 @@ Ant</a> 1.8 or later.</p>
<dl>
<dt>Dependencies:</dt>
<dd>
-<p>If you are developing in Eclipse with ADT, note that the SDK Tools r9 is
+<p>If you are developing in Eclipse with ADT, note that this version of SDK Tools is
designed for use with ADT 9.0.0 and later. After installing SDK Tools r9, we
highly recommend updating your ADT Plugin to 9.0.0.</p>
@@ -1044,7 +1085,7 @@ resolve the performance issues and it will improve in future releases.</li>
<dl>
<dt>Dependencies:</dt>
<dd>
-<p>If you are developing in Eclipse with ADT, note that SDK Tools r8 is
+<p>If you are developing in Eclipse with ADT, note that this version of SDK Tools is
designed for use with ADT 8.0.0 and later. After installing SDK Tools r8, we
highly recommend updating your ADT Plugin to 8.0.0.</p>
@@ -1112,7 +1153,7 @@ documentation. </li>
<dl>
<dt>Dependencies:</dt>
<dd>
-<p>If you are developing in Eclipse with ADT, note that SDK Tools r7 is
+<p>If you are developing in Eclipse with ADT, note that this version of SDK Tools is
designed for use with ADT 0.9.8 and later. After installing SDK Tools r7, we
highly recommend updating your ADT Plugin to 0.9.8.</p>
</dd>
@@ -1146,7 +1187,7 @@ decimal point.</li>
<dl>
<dt>Dependencies:</dt>
<dd>
-<p>If you are developing in Eclipse with ADT, note that SDK Tools r6 is
+<p>If you are developing in Eclipse with ADT, note that this version of SDK Tools is
designed for use with ADT 0.9.7 and later. After installing SDK Tools r6, we
highly recommend updating your ADT Plugin to 0.9.7.</p>
</dd>
@@ -1177,7 +1218,7 @@ provides the equivalent library project support.</p>
<dl>
<dt>Dependencies:</dt>
<dd><ul>
-<li>If you are developing in Eclipse with ADT, note that SDK Tools r5 is
+<li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is
designed for use with ADT 0.9.6 and later. After installing SDK Tools r5, we
highly recommend updating your ADT Plugin to 0.9.6.</li>
<li>For Mac OS platforms, OS X 10.4.x (Tiger) is no longer
@@ -1225,7 +1266,7 @@ officially supported. </li>
<div class="toggle-content-toggleme">
<dl>
<dt>Dependencies:</dt>
-<dd><p>SDK Tools r4 is compatible with ADT 0.9.5 and later, but not
+<dd><p>This version of SDK Tools is compatible with ADT 0.9.5 and later, but not
compatible with earlier versions. If you are developing in Eclipse with ADT, you
<strong>must</strong> update your ADT plugin to version 0.9.5 or higher if you
install SDK Tools r4 in your SDK. </p></dd>
@@ -1275,7 +1316,7 @@ skin name specified.</li>
<div class="toggle-content-toggleme">
<dl>
<dt>Dependencies:</dt>
-<dd><p>SDK Tools r3 is compatible with ADT 0.9.4 and later, but not
+<dd><p>This version of SDK Tools is compatible with ADT 0.9.4 and later, but not
compatible with earlier versions. If you are developing in Eclipse with ADT, you
<strong>must</strong> update your ADT plugin to version 0.9.4 or higher if you
install SDK Tools r3 in your SDK.</p>
@@ -1329,4 +1370,3 @@ href="/tools/help/layoutopt.html">layoutopt</a>.</p>
</dl>
</div>
</div>
-
diff --git a/docs/html/tools/support-library/features.jd b/docs/html/tools/support-library/features.jd
index 12fc0ae729d4..8d25d96c9116 100644
--- a/docs/html/tools/support-library/features.jd
+++ b/docs/html/tools/support-library/features.jd
@@ -12,6 +12,7 @@ page.title=Support Library Features
<ol>
<li><a href="#v7-appcompat">v7 appcompat library</a></li>
<li><a href="#v7-gridlayout">v7 gridlayout library</a></li>
+ <li><a href="#v7-mediarouter">v7 mediarouter library</a></li>
</ol>
</li>
<li><a href="#v13">v13 Support Library</a></li>
@@ -148,7 +149,6 @@ com.android.support:support-v4:18.0.+
<p>This dependency notation specifies the release version 18.0.0 or higher.</p>
-
<h2 id="v7">v7 Libraries</h2>
<p>There are several libraries designed to be used with Android 2.1 (API level 7) and higher.
@@ -197,9 +197,7 @@ com.android.support:support-v4:18.0.+
com.android.support:appcompat-v7:18.0.+
</pre>
-<p>This dependency notation specifies release version 18.0.0 or higher.</p>
-
-
+<p>This dependency notation specifies the release version 18.0.0 or higher.</p>
<h3 id="v7-gridlayout">v7 gridlayout library</h3>
@@ -220,7 +218,48 @@ com.android.support:appcompat-v7:18.0.+
com.android.support:gridlayout-v7:18.0.+
</pre>
-<p>This dependency notation specifies release version 18.0.0 or higher.</p>
+<p>This dependency notation specifies the release version 18.0.0 or higher.</p>
+
+<h3 id="v7-mediarouter">v7 mediarouter library</h3>
+
+<p>This library provides {@link android.support.v7.media.MediaRouter}, {@link
+android.support.v7.media.MediaRouteProvider}, and related media classes that
+support the <a href="https://developers.google.com/cast/">Google Cast
+developer preview</a>. </p>
+
+<p>In general, the APIs in the v7 mediarouter library provide a means of
+controlling the routing of media channels and streams from the current device to
+external screens, speakers, and other destination devices. The library includes
+APIs for publishing app-specific media route providers, for discovering and
+selecting destination devices, for checking media status, and more. For detailed
+information about the v7 mediarouter library APIs, see the
+{@link android.support.v7.media android.support.v7.media} package in the API
+reference.</p>
+
+<p>The v7 mediarouter library is located in the
+<code>&lt;sdk&gt;/extras/android/support/v7/mediarouter/</code> directory after
+you download the Android Support Library. It's provided as a library project
+with a dependency on the v7 appcompat library, so you'll need to include both
+libraries in your build path when setting up your project. For more information
+on how to set up your project, follow the instructions in <a
+href="{@docRoot}tools/support-library/setup.html#libs-with-res">adding libraries
+with resources</a>. If you are developing in Eclipse/ADT, make sure to include
+both the <code>android-support-v7-mediarouter.jar</code> and
+<code>android-support-v7-appcompat.jar</code> files.</p>
+
+<p>If you are using Android Studio, all you need to do is specify the Gradle build
+script dependency identifier <code>com.android.support:support-v7-mediarouter:&lt;revision&gt;</code>,
+where "18.0.0" is the minimum revision at which the library is available. For example:</p>
+
+<pre>
+com.android.support:support-v7-mediarouter:18.0.0
+</pre>
+
+<p class="caution">The v7 mediarouter library APIs introduced in Support Library
+r18 are subject to change in later revisions of the Support Library. At this
+time, we recommend using the library only in connection with the <a
+href="https://developers.google.com/cast/">Google Cast
+developer preview</a>. </p>
<h2 id="v13">v13 Support Library</h2>
diff --git a/docs/html/tools/support-library/index.jd b/docs/html/tools/support-library/index.jd
index 87ed7f17b13e..06c7a3ff638a 100644
--- a/docs/html/tools/support-library/index.jd
+++ b/docs/html/tools/support-library/index.jd
@@ -22,8 +22,7 @@ page.title=Support Library
</div>
</div>
-<p>The Android Support Library package is a set of code libraries that provide useful and important
- features for Android applications in addition to the framework APIs. These libraries provide
+<p>The Android Support Library package is a set of code libraries that provide
backward-compatible versions of Android framework APIs as well as features that are only available
through the library APIs. Each Support Library is backward-compatible to a specific Android API
level. This design means that your applications can use the libraries' features and still be
@@ -36,7 +35,8 @@ page.title=Support Library
<h2 id="overview">Overview</h2>
<p>Including the Support Libraries in your Android project is considered a best practice for
- application developers. Using the features they provide can help you improve the look of your
+ application developers, depending on the range of platform versions your app is targeting
+ and the APIs that it uses. Using the features the libraries provide can help you improve the look of your
application, increase performance and broaden the reach of your application to more users.
If you use the Android
<a href="{@docRoot}tools/projects/templates.html">code template</a> tools, you will notice that
@@ -136,6 +136,24 @@ page.title=Support Library
</ul>
</dd>
+ <dt>New v7 mediarouter library:</dt>
+ <dd>
+ <p>Added a new mediarouter library that provides support for the <a
+ href="https://developers.google.com/cast/">Google Cast developer preview</a>.
+ The v7 mediarouter library APIs provide a means of controlling the routing of
+ media channels and streams from the current device to external screens,
+ speakers, and other destination devices, with compatibility back to Android 2.1
+ (API level 7). See <a
+ href="{@docRoot}tools/support-library/features.html#v7-mediarouter">V7
+ mediarouter library</a> for more information. </p>
+
+ <p class="caution">The v7 mediarouter library APIs introduced in Support
+ Library r18 are subject to change in later revisions of the Support
+ Library. At this time, we recommend using the library only in connection
+ with the <a href="https://developers.google.com/cast/">Google Cast
+ developer preview</a>.</p>
+ </dd>
+
</dl>
</div>
</div>
diff --git a/docs/html/training/basics/intents/sending.jd b/docs/html/training/basics/intents/sending.jd
index 37a06f16821b..1646b9138b37 100644
--- a/docs/html/training/basics/intents/sending.jd
+++ b/docs/html/training/basics/intents/sending.jd
@@ -172,7 +172,7 @@ Play</a>).</p>
<h2 id="StartActivity">Start an Activity with the Intent</h2>
-<div class="figure" style="width:200px">
+<div class="figure" style="width:200px;margin-top:-10px">
<img src="{@docRoot}images/training/basics/intents-choice.png" alt="" />
<p class="img-caption"><strong>Figure 1.</strong> Example of the selection dialog that appears
when more than one app can handle an intent.</p>
@@ -211,11 +211,9 @@ if (isIntentSafe) {
<h2 id="AppChooser">Show an App Chooser</h2>
-<div class="figure" style="width:200px">
+<div class="figure" style="width:200px;margin-top:-10px">
<img src="{@docRoot}images/training/basics/intent-chooser.png" alt="" />
- <p class="img-caption"><strong>Figure 2.</strong> Example of the chooser dialog that appears
-when you use {@link android.content.Intent#createChooser createChooser()} to ensure
-that the user is always shown a list of apps that respond to your intent.</p>
+ <p class="img-caption"><strong>Figure 2.</strong> A chooser dialog.</p>
</div>
<p>Notice that when you start an activity by passing your {@link android.content.Intent} to {@link
@@ -223,11 +221,13 @@ android.app.Activity#startActivity startActivity()} and there is more than one a
the intent, the user can select which app to use by default (by selecting a checkbox at the bottom
of the dialog; see figure 1). This is nice when performing an action for which the user
generally wants to use the same app every time, such as when opening a web page (users
-likely use just one web browser) or taking a photo (users likely prefer one camera). However, if
-the action to be performed could be handled by multiple apps and the user might
+likely use just one web browser) or taking a photo (users likely prefer one camera).</p>
+
+<p>However, if the action to be performed could be handled by multiple apps and the user might
prefer a different app each time&mdash;such as a "share" action, for which users might have several
-apps through which they might share an item&mdash;you should explicitly show a chooser dialog,
-which forces the user to select which app to use for the action every time (the user cannot select a
+apps through which they might share an item&mdash;you should explicitly show a chooser dialog
+as shown in figure 2. The chooser dialog
+forces the user to select which app to use for the action every time (the user cannot select a
default app for the action).</p>
<p>To show the chooser, create an {@link android.content.Intent} using {@link
@@ -238,8 +238,9 @@ android.app.Activity#startActivity startActivity()}. For example:</p>
Intent intent = new Intent(Intent.ACTION_SEND);
...
-// Always use string resources for UI text. This says something like "Share this photo with"
-String title = getResources().getText(R.string.chooser_title);
+// Always use string resources for UI text.
+// This says something like "Share this photo with"
+String title = (String) getResources().getText(R.string.chooser_title);
// Create and start the chooser
Intent chooser = Intent.createChooser(intent, title);
startActivity(chooser);
diff --git a/docs/html/training/displaying-bitmaps/cache-bitmap.jd b/docs/html/training/displaying-bitmaps/cache-bitmap.jd
index b1608c3a49bc..25efe1ebbae0 100644
--- a/docs/html/training/displaying-bitmaps/cache-bitmap.jd
+++ b/docs/html/training/displaying-bitmaps/cache-bitmap.jd
@@ -324,14 +324,14 @@ private LruCache&lt;String, Bitmap&gt; mMemoryCache;
&#64;Override
protected void onCreate(Bundle savedInstanceState) {
...
- RetainFragment mRetainFragment =
+ RetainFragment retainFragment =
RetainFragment.findOrCreateRetainFragment(getFragmentManager());
- mMemoryCache = RetainFragment.mRetainedCache;
+ mMemoryCache = retainFragment.mRetainedCache;
if (mMemoryCache == null) {
mMemoryCache = new LruCache&lt;String, Bitmap&gt;(cacheSize) {
... // Initialize cache here as usual
}
- mRetainFragment.mRetainedCache = mMemoryCache;
+ retainFragment.mRetainedCache = mMemoryCache;
}
...
}
diff --git a/docs/html/training/index.jd b/docs/html/training/index.jd
index 82fbd1618b4a..72ad018949aa 100644
--- a/docs/html/training/index.jd
+++ b/docs/html/training/index.jd
@@ -3,14 +3,7 @@ page.trainingcourse=true
page.metaDescription=Android Training provides a collection of classes that aim to help you build great apps for Android. Each class explains the steps required to solve a problem or implement a feature using code snippets and sample code for you to use in your apps.
@jd:body
-<div id="butterbar-wrapper" >
- <div id="butterbar" >
- <div id="butterbar-message">
-<a target="_blank" href="https://docs.google.com/a/google.com/forms/d/1EHLPGqhbxj2HungHRRN4_0K9TGpc-Izy-u46vBDgS8Q/viewform">
- Take the Android Developer Survey</a>
- </div>
- </div>
-</div>
+
<p>Welcome to Training for Android developers. Here you'll find sets of lessons within classes
that describe how to accomplish a specific task with code samples you can re-use in your app.
diff --git a/graphics/java/android/graphics/Color.java b/graphics/java/android/graphics/Color.java
index 6a4e89a3ba00..f526d2995f6d 100644
--- a/graphics/java/android/graphics/Color.java
+++ b/graphics/java/android/graphics/Color.java
@@ -406,18 +406,18 @@ public class Color {
sColorNameMap.put("yellow", YELLOW);
sColorNameMap.put("cyan", CYAN);
sColorNameMap.put("magenta", MAGENTA);
- sColorNameMap.put("aqua", 0x00FFFF);
- sColorNameMap.put("fuchsia", 0xFF00FF);
+ sColorNameMap.put("aqua", 0xFF00FFFF);
+ sColorNameMap.put("fuchsia", 0xFFFF00FF);
sColorNameMap.put("darkgrey", DKGRAY);
sColorNameMap.put("grey", GRAY);
sColorNameMap.put("lightgrey", LTGRAY);
- sColorNameMap.put("lime", 0x00FF00);
- sColorNameMap.put("maroon", 0x800000);
- sColorNameMap.put("navy", 0x000080);
- sColorNameMap.put("olive", 0x808000);
- sColorNameMap.put("purple", 0x800080);
- sColorNameMap.put("silver", 0xC0C0C0);
- sColorNameMap.put("teal", 0x008080);
+ sColorNameMap.put("lime", 0xFF00FF00);
+ sColorNameMap.put("maroon", 0xFF800000);
+ sColorNameMap.put("navy", 0xFF000080);
+ sColorNameMap.put("olive", 0xFF808000);
+ sColorNameMap.put("purple", 0xFF800080);
+ sColorNameMap.put("silver", 0xFFC0C0C0);
+ sColorNameMap.put("teal", 0xFF008080);
}
}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 1485d8d1d810..e87839b4f756 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -105,9 +105,17 @@ public class Paint {
public static final int SUBPIXEL_TEXT_FLAG = 0x80;
/** bit mask for the flag enabling device kerning for text */
public static final int DEV_KERN_TEXT_FLAG = 0x100;
+ /** @hide bit mask for the flag enabling subpixel glyph rendering for text */
+ public static final int LCD_RENDER_TEXT_FLAG = 0x200;
+ /** bit mask for the flag enabling embedded bitmap strikes for text */
+ public static final int EMBEDDED_BITMAP_TEXT_FLAG = 0x400;
+ /** @hide bit mask for the flag forcing freetype's autohinter on for text */
+ public static final int AUTO_HINTING_TEXT_FLAG = 0x800;
+ /** @hide bit mask for the flag enabling vertical rendering for text */
+ public static final int VERTICAL_TEXT_FLAG = 0x1000;
// we use this when we first create a paint
- static final int DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG;
+ static final int DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG | EMBEDDED_BITMAP_TEXT_FLAG;
/**
* Option for {@link #setHinting}: disable hinting.
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index 6cc9d8f14524..e3a7e2bfa84e 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.java
@@ -261,6 +261,13 @@ public class InsetDrawable extends Drawable implements Drawable.Callback
return this;
}
+ /**
+ * Returns the drawable wrapped by this InsetDrawable. May be null.
+ */
+ public Drawable getDrawable() {
+ return mInsetState.mDrawable;
+ }
+
final static class InsetState extends ConstantState {
Drawable mDrawable;
int mChangingConfigurations;
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index a305fc3f0c91..97afa59bd7cd 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -479,7 +479,7 @@ private:
const uint32_t* mEntries;
const uint32_t* mEntryStyles;
const void* mStrings;
- char16_t** mCache;
+ char16_t mutable** mCache;
uint32_t mStringPoolSize; // number of uint16_t
const uint32_t* mStyles;
uint32_t mStylePoolSize; // number of uint32_t
@@ -678,11 +678,15 @@ public:
// Returns -1 if no namespace, -2 if idx out of range.
int32_t getAttributeNamespaceID(size_t idx) const;
const uint16_t* getAttributeNamespace(size_t idx, size_t* outLen) const;
-
+
int32_t getAttributeNameID(size_t idx) const;
const uint16_t* getAttributeName(size_t idx, size_t* outLen) const;
uint32_t getAttributeNameResID(size_t idx) const;
-
+
+ // These will work only if the underlying string pool is UTF-8.
+ const char* getAttributeNamespace8(size_t idx, size_t* outLen) const;
+ const char* getAttributeName8(size_t idx, size_t* outLen) const;
+
int32_t getAttributeValueStringID(size_t idx) const;
const uint16_t* getAttributeStringValue(size_t idx, size_t* outLen) const;
@@ -1294,12 +1298,14 @@ public:
const char16_t* package;
size_t packageLen;
const char16_t* type;
+ const char* type8;
size_t typeLen;
const char16_t* name;
+ const char* name8;
size_t nameLen;
};
- bool getResourceName(uint32_t resID, resource_name* outName) const;
+ bool getResourceName(uint32_t resID, bool allowUtf8, resource_name* outName) const;
/**
* Retrieve the value of a resource. If the resource is found, returns a
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 1128e024aaf8..1cc3563e0f3d 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -35,7 +35,7 @@
#define INT32_MAX ((int32_t)(2147483647))
#endif
-#define POOL_NOISY(x) //x
+#define STRING_POOL_NOISY(x) //x
#define XML_NOISY(x) //x
#define TABLE_NOISY(x) //x
#define TABLE_GETENTRY(x) //x
@@ -378,7 +378,6 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData)
size_t charSize;
if (mHeader->flags&ResStringPool_header::UTF8_FLAG) {
charSize = sizeof(uint8_t);
- mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t**));
} else {
charSize = sizeof(char16_t);
}
@@ -593,6 +592,23 @@ const uint16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const
if ((uint32_t)(u8str+u8len-strings) < mStringPoolSize) {
AutoMutex lock(mDecodeLock);
+ if (mCache == NULL) {
+#ifndef HAVE_ANDROID_OS
+ STRING_POOL_NOISY(ALOGI("CREATING STRING CACHE OF %d bytes",
+ mHeader->stringCount*sizeof(char16_t**)));
+#else
+ // We do not want to be in this case when actually running Android.
+ ALOGW("CREATING STRING CACHE OF %d bytes",
+ mHeader->stringCount*sizeof(char16_t**));
+#endif
+ mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t**));
+ if (mCache == NULL) {
+ ALOGW("No memory trying to allocate decode cache table of %d bytes\n",
+ (int)(mHeader->stringCount*sizeof(char16_t**)));
+ return NULL;
+ }
+ }
+
if (mCache[idx] != NULL) {
return mCache[idx];
}
@@ -612,6 +628,7 @@ const uint16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const
return NULL;
}
+ STRING_POOL_NOISY(ALOGI("Caching UTF8 string: %s", u8str));
utf8_to_utf16(u8str, u8len, u16str);
mCache[idx] = u16str;
return u16str;
@@ -633,20 +650,20 @@ const uint16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const
const char* ResStringPool::string8At(size_t idx, size_t* outLen) const
{
if (mError == NO_ERROR && idx < mHeader->stringCount) {
- const bool isUTF8 = (mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0;
- const uint32_t off = mEntries[idx]/(isUTF8?sizeof(char):sizeof(char16_t));
+ if ((mHeader->flags&ResStringPool_header::UTF8_FLAG) == 0) {
+ return NULL;
+ }
+ const uint32_t off = mEntries[idx]/sizeof(char);
if (off < (mStringPoolSize-1)) {
- if (isUTF8) {
- const uint8_t* strings = (uint8_t*)mStrings;
- const uint8_t* str = strings+off;
- *outLen = decodeLength(&str);
- size_t encLen = decodeLength(&str);
- if ((uint32_t)(str+encLen-strings) < mStringPoolSize) {
- return (const char*)str;
- } else {
- ALOGW("Bad string block: string #%d extends to %d, past end at %d\n",
- (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize);
- }
+ const uint8_t* strings = (uint8_t*)mStrings;
+ const uint8_t* str = strings+off;
+ *outLen = decodeLength(&str);
+ size_t encLen = decodeLength(&str);
+ if ((uint32_t)(str+encLen-strings) < mStringPoolSize) {
+ return (const char*)str;
+ } else {
+ ALOGW("Bad string block: string #%d extends to %d, past end at %d\n",
+ (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize);
}
} else {
ALOGW("Bad string block: string #%d entry is at %d, past end at %d\n",
@@ -695,45 +712,104 @@ ssize_t ResStringPool::indexOfString(const char16_t* str, size_t strLen) const
size_t len;
- // TODO optimize searching for UTF-8 strings taking into account
- // the cache fill to determine when to convert the searched-for
- // string key to UTF-8.
-
- if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
- // Do a binary search for the string...
- ssize_t l = 0;
- ssize_t h = mHeader->stringCount-1;
-
- ssize_t mid;
- while (l <= h) {
- mid = l + (h - l)/2;
- const char16_t* s = stringAt(mid, &len);
- int c = s ? strzcmp16(s, len, str, strLen) : -1;
- POOL_NOISY(printf("Looking for %s, at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
- String8(str).string(),
- String8(s).string(),
- c, (int)l, (int)mid, (int)h));
- if (c == 0) {
- return mid;
- } else if (c < 0) {
- l = mid + 1;
- } else {
- h = mid - 1;
+ if ((mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0) {
+ STRING_POOL_NOISY(ALOGI("indexOfString UTF-8: %s", String8(str, strLen).string()));
+
+ // The string pool contains UTF 8 strings; we don't want to cause
+ // temporary UTF-16 strings to be created as we search.
+ if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
+ // Do a binary search for the string... this is a little tricky,
+ // because the strings are sorted with strzcmp16(). So to match
+ // the ordering, we need to convert strings in the pool to UTF-16.
+ // But we don't want to hit the cache, so instead we will have a
+ // local temporary allocation for the conversions.
+ char16_t* convBuffer = (char16_t*)malloc(strLen+4);
+ ssize_t l = 0;
+ ssize_t h = mHeader->stringCount-1;
+
+ ssize_t mid;
+ while (l <= h) {
+ mid = l + (h - l)/2;
+ const uint8_t* s = (const uint8_t*)string8At(mid, &len);
+ int c;
+ if (s != NULL) {
+ char16_t* end = utf8_to_utf16_n(s, len, convBuffer, strLen+3);
+ *end = 0;
+ c = strzcmp16(convBuffer, end-convBuffer, str, strLen);
+ } else {
+ c = -1;
+ }
+ STRING_POOL_NOISY(ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
+ (const char*)s, c, (int)l, (int)mid, (int)h));
+ if (c == 0) {
+ STRING_POOL_NOISY(ALOGI("MATCH!"));
+ free(convBuffer);
+ return mid;
+ } else if (c < 0) {
+ l = mid + 1;
+ } else {
+ h = mid - 1;
+ }
+ }
+ free(convBuffer);
+ } else {
+ // It is unusual to get the ID from an unsorted string block...
+ // most often this happens because we want to get IDs for style
+ // span tags; since those always appear at the end of the string
+ // block, start searching at the back.
+ String8 str8(str, strLen);
+ const size_t str8Len = str8.size();
+ for (int i=mHeader->stringCount-1; i>=0; i--) {
+ const char* s = string8At(i, &len);
+ STRING_POOL_NOISY(ALOGI("Looking at %s, i=%d\n",
+ String8(s).string(),
+ i));
+ if (s && str8Len == len && memcmp(s, str8.string(), str8Len) == 0) {
+ STRING_POOL_NOISY(ALOGI("MATCH!"));
+ return i;
+ }
}
}
+
} else {
- // It is unusual to get the ID from an unsorted string block...
- // most often this happens because we want to get IDs for style
- // span tags; since those always appear at the end of the string
- // block, start searching at the back.
- for (int i=mHeader->stringCount-1; i>=0; i--) {
- const char16_t* s = stringAt(i, &len);
- POOL_NOISY(printf("Looking for %s, at %s, i=%d\n",
- String8(str, strLen).string(),
- String8(s).string(),
- i));
- if (s && strzcmp16(s, len, str, strLen) == 0) {
- return i;
+ STRING_POOL_NOISY(ALOGI("indexOfString UTF-16: %s", String8(str, strLen).string()));
+
+ if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
+ // Do a binary search for the string...
+ ssize_t l = 0;
+ ssize_t h = mHeader->stringCount-1;
+
+ ssize_t mid;
+ while (l <= h) {
+ mid = l + (h - l)/2;
+ const char16_t* s = stringAt(mid, &len);
+ int c = s ? strzcmp16(s, len, str, strLen) : -1;
+ STRING_POOL_NOISY(ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
+ String8(s).string(),
+ c, (int)l, (int)mid, (int)h));
+ if (c == 0) {
+ STRING_POOL_NOISY(ALOGI("MATCH!"));
+ return mid;
+ } else if (c < 0) {
+ l = mid + 1;
+ } else {
+ h = mid - 1;
+ }
+ }
+ } else {
+ // It is unusual to get the ID from an unsorted string block...
+ // most often this happens because we want to get IDs for style
+ // span tags; since those always appear at the end of the string
+ // block, start searching at the back.
+ for (int i=mHeader->stringCount-1; i>=0; i--) {
+ const char16_t* s = stringAt(i, &len);
+ STRING_POOL_NOISY(ALOGI("Looking at %s, i=%d\n",
+ String8(s).string(),
+ i));
+ if (s && strLen == len && strzcmp16(s, len, str, strLen) == 0) {
+ STRING_POOL_NOISY(ALOGI("MATCH!"));
+ return i;
+ }
}
}
}
@@ -936,6 +1012,14 @@ const uint16_t* ResXMLParser::getAttributeNamespace(size_t idx, size_t* outLen)
return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
}
+const char* ResXMLParser::getAttributeNamespace8(size_t idx, size_t* outLen) const
+{
+ int32_t id = getAttributeNamespaceID(idx);
+ //printf("attribute namespace=%d idx=%d event=%p\n", id, idx, mEventCode);
+ //XML_NOISY(printf("getAttributeNamespace 0x%x=0x%x\n", idx, id));
+ return id >= 0 ? mTree.mStrings.string8At(id, outLen) : NULL;
+}
+
int32_t ResXMLParser::getAttributeNameID(size_t idx) const
{
if (mEventCode == START_TAG) {
@@ -959,6 +1043,14 @@ const uint16_t* ResXMLParser::getAttributeName(size_t idx, size_t* outLen) const
return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
}
+const char* ResXMLParser::getAttributeName8(size_t idx, size_t* outLen) const
+{
+ int32_t id = getAttributeNameID(idx);
+ //printf("attribute name=%d idx=%d event=%p\n", id, idx, mEventCode);
+ //XML_NOISY(printf("getAttributeName 0x%x=0x%x\n", idx, id));
+ return id >= 0 ? mTree.mStrings.string8At(id, outLen) : NULL;
+}
+
uint32_t ResXMLParser::getAttributeNameResID(size_t idx) const
{
int32_t id = getAttributeNameID(idx);
@@ -1048,22 +1140,67 @@ ssize_t ResXMLParser::indexOfAttribute(const char16_t* ns, size_t nsLen,
const char16_t* attr, size_t attrLen) const
{
if (mEventCode == START_TAG) {
+ if (attr == NULL) {
+ return NAME_NOT_FOUND;
+ }
const size_t N = getAttributeCount();
- for (size_t i=0; i<N; i++) {
- size_t curNsLen, curAttrLen;
- const char16_t* curNs = getAttributeNamespace(i, &curNsLen);
- const char16_t* curAttr = getAttributeName(i, &curAttrLen);
- //printf("%d: ns=%p attr=%p curNs=%p curAttr=%p\n",
- // i, ns, attr, curNs, curAttr);
- //printf(" --> attr=%s, curAttr=%s\n",
- // String8(attr).string(), String8(curAttr).string());
- if (attr && curAttr && (strzcmp16(attr, attrLen, curAttr, curAttrLen) == 0)) {
- if (ns == NULL) {
- if (curNs == NULL) return i;
- } else if (curNs != NULL) {
- //printf(" --> ns=%s, curNs=%s\n",
- // String8(ns).string(), String8(curNs).string());
- if (strzcmp16(ns, nsLen, curNs, curNsLen) == 0) return i;
+ if (mTree.mStrings.isUTF8()) {
+ String8 ns8, attr8;
+ if (ns != NULL) {
+ ns8 = String8(ns, nsLen);
+ }
+ attr8 = String8(attr, attrLen);
+ STRING_POOL_NOISY(ALOGI("indexOfAttribute UTF8 %s (%d) / %s (%d)", ns8.string(), nsLen,
+ attr8.string(), attrLen));
+ for (size_t i=0; i<N; i++) {
+ size_t curNsLen = 0, curAttrLen = 0;
+ const char* curNs = getAttributeNamespace8(i, &curNsLen);
+ const char* curAttr = getAttributeName8(i, &curAttrLen);
+ STRING_POOL_NOISY(ALOGI(" curNs=%s (%d), curAttr=%s (%d)", curNs, curNsLen,
+ curAttr, curAttrLen));
+ if (curAttr != NULL && curNsLen == nsLen && curAttrLen == attrLen
+ && memcmp(attr8.string(), curAttr, attrLen) == 0) {
+ if (ns == NULL) {
+ if (curNs == NULL) {
+ STRING_POOL_NOISY(ALOGI(" FOUND!"));
+ return i;
+ }
+ } else if (curNs != NULL) {
+ //printf(" --> ns=%s, curNs=%s\n",
+ // String8(ns).string(), String8(curNs).string());
+ if (memcmp(ns8.string(), curNs, nsLen) == 0) {
+ STRING_POOL_NOISY(ALOGI(" FOUND!"));
+ return i;
+ }
+ }
+ }
+ }
+ } else {
+ STRING_POOL_NOISY(ALOGI("indexOfAttribute UTF16 %s (%d) / %s (%d)",
+ String8(ns, nsLen).string(), nsLen,
+ String8(attr, attrLen).string(), attrLen));
+ for (size_t i=0; i<N; i++) {
+ size_t curNsLen = 0, curAttrLen = 0;
+ const char16_t* curNs = getAttributeNamespace(i, &curNsLen);
+ const char16_t* curAttr = getAttributeName(i, &curAttrLen);
+ STRING_POOL_NOISY(ALOGI(" curNs=%s (%d), curAttr=%s (%d)",
+ String8(curNs, curNsLen).string(), curNsLen,
+ String8(curAttr, curAttrLen).string(), curAttrLen));
+ if (curAttr != NULL && curNsLen == nsLen && curAttrLen == attrLen
+ && (memcmp(attr, curAttr, attrLen*sizeof(char16_t)) == 0)) {
+ if (ns == NULL) {
+ if (curNs == NULL) {
+ STRING_POOL_NOISY(ALOGI(" FOUND!"));
+ return i;
+ }
+ } else if (curNs != NULL) {
+ //printf(" --> ns=%s, curNs=%s\n",
+ // String8(ns).string(), String8(curNs).string());
+ if (memcmp(ns, curNs, nsLen*sizeof(char16_t)) == 0) {
+ STRING_POOL_NOISY(ALOGI(" FOUND!"));
+ return i;
+ }
+ }
}
}
}
@@ -2940,7 +3077,7 @@ void ResTable::uninit()
mHeaders.clear();
}
-bool ResTable::getResourceName(uint32_t resID, resource_name* outName) const
+bool ResTable::getResourceName(uint32_t resID, bool allowUtf8, resource_name* outName) const
{
if (mError != NO_ERROR) {
return false;
@@ -2980,13 +3117,28 @@ bool ResTable::getResourceName(uint32_t resID, resource_name* outName) const
outName->package = grp->name.string();
outName->packageLen = grp->name.size();
- outName->type = grp->basePackage->typeStrings.stringAt(t, &outName->typeLen);
- outName->name = grp->basePackage->keyStrings.stringAt(
- dtohl(entry->key.index), &outName->nameLen);
-
- // If we have a bad index for some reason, we should abort.
- if (outName->type == NULL || outName->name == NULL) {
- return false;
+ if (allowUtf8) {
+ outName->type8 = grp->basePackage->typeStrings.string8At(t, &outName->typeLen);
+ outName->name8 = grp->basePackage->keyStrings.string8At(
+ dtohl(entry->key.index), &outName->nameLen);
+ } else {
+ outName->type8 = NULL;
+ outName->name8 = NULL;
+ }
+ if (outName->type8 == NULL) {
+ outName->type = grp->basePackage->typeStrings.stringAt(t, &outName->typeLen);
+ // If we have a bad index for some reason, we should abort.
+ if (outName->type == NULL) {
+ return false;
+ }
+ }
+ if (outName->name8 == NULL) {
+ outName->name = grp->basePackage->keyStrings.stringAt(
+ dtohl(entry->key.index), &outName->nameLen);
+ // If we have a bad index for some reason, we should abort.
+ if (outName->name == NULL) {
+ return false;
+ }
}
return true;
@@ -4485,7 +4637,7 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString,
while (cnt > 0) {
if (!Res_INTERNALID(bag->map.name.ident)) {
//printf("Trying attr #%08x\n", bag->map.name.ident);
- if (getResourceName(bag->map.name.ident, &rname)) {
+ if (getResourceName(bag->map.name.ident, false, &rname)) {
#if 0
printf("Matching %s against %s (0x%08x)\n",
String8(s, len).string(),
@@ -4538,7 +4690,7 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString,
for (i=0; i<cnt; i++, bagi++) {
if (!Res_INTERNALID(bagi->map.name.ident)) {
//printf("Trying attr #%08x\n", bagi->map.name.ident);
- if (getResourceName(bagi->map.name.ident, &rname)) {
+ if (getResourceName(bagi->map.name.ident, false, &rname)) {
#if 0
printf("Matching %s against %s (0x%08x)\n",
String8(start,pos-start).string(),
@@ -5216,7 +5368,7 @@ status_t ResTable::createIdmap(const ResTable& overlay, uint32_t originalCrc, ui
| (0x00ff0000 & ((typeIndex+1)<<16))
| (0x0000ffff & (entryIndex));
resource_name resName;
- if (!this->getResourceName(resID, &resName)) {
+ if (!this->getResourceName(resID, true, &resName)) {
ALOGW("idmap: resource 0x%08x has spec but lacks values, skipping\n", resID);
// add dummy value, or trimming leading/trailing zeroes later will fail
vector.push(0);
@@ -5483,12 +5635,23 @@ void ResTable::print(bool inclValues) const
| (0x00ff0000 & ((typeIndex+1)<<16))
| (0x0000ffff & (entryIndex));
resource_name resName;
- if (this->getResourceName(resID, &resName)) {
+ if (this->getResourceName(resID, true, &resName)) {
+ String8 type8;
+ String8 name8;
+ if (resName.type8 != NULL) {
+ type8 = String8(resName.type8, resName.typeLen);
+ } else {
+ type8 = String8(resName.type, resName.typeLen);
+ }
+ if (resName.name8 != NULL) {
+ name8 = String8(resName.name8, resName.nameLen);
+ } else {
+ name8 = String8(resName.name, resName.nameLen);
+ }
printf(" spec resource 0x%08x %s:%s/%s: flags=0x%08x\n",
resID,
CHAR16_TO_CSTR(resName.package, resName.packageLen),
- CHAR16_TO_CSTR(resName.type, resName.typeLen),
- CHAR16_TO_CSTR(resName.name, resName.nameLen),
+ type8.string(), name8.string(),
dtohl(typeConfigs->typeSpecFlags[entryIndex]));
} else {
printf(" INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID);
@@ -5531,11 +5694,22 @@ void ResTable::print(bool inclValues) const
| (0x00ff0000 & ((typeIndex+1)<<16))
| (0x0000ffff & (entryIndex));
resource_name resName;
- if (this->getResourceName(resID, &resName)) {
+ if (this->getResourceName(resID, true, &resName)) {
+ String8 type8;
+ String8 name8;
+ if (resName.type8 != NULL) {
+ type8 = String8(resName.type8, resName.typeLen);
+ } else {
+ type8 = String8(resName.type, resName.typeLen);
+ }
+ if (resName.name8 != NULL) {
+ name8 = String8(resName.name8, resName.nameLen);
+ } else {
+ name8 = String8(resName.name, resName.nameLen);
+ }
printf(" resource 0x%08x %s:%s/%s: ", resID,
CHAR16_TO_CSTR(resName.package, resName.packageLen),
- CHAR16_TO_CSTR(resName.type, resName.typeLen),
- CHAR16_TO_CSTR(resName.name, resName.nameLen));
+ type8.string(), name8.string());
} else {
printf(" INVALID RESOURCE 0x%08x: ", resID);
}
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index 6ac637e2a54d..df966e16a286 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -256,8 +256,12 @@ void Caches::dumpMemoryUsage(String8 &log) {
log.appendFormat(" PatchCache %8d / %8d\n",
patchCache.getSize(), patchCache.getMaxSize());
for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) {
- const uint32_t size = fontRenderer->getFontRendererSize(i);
- log.appendFormat(" FontRenderer %d %8d / %8d\n", i, size, size);
+ const uint32_t sizeA8 = fontRenderer->getFontRendererSize(i, GL_ALPHA);
+ const uint32_t sizeRGBA = fontRenderer->getFontRendererSize(i, GL_RGBA);
+ log.appendFormat(" FontRenderer %d A8 %8d / %8d\n", i, sizeA8, sizeA8);
+ log.appendFormat(" FontRenderer %d RGBA %8d / %8d\n", i, sizeRGBA, sizeRGBA);
+ log.appendFormat(" FontRenderer %d total %8d / %8d\n", i, sizeA8 + sizeRGBA,
+ sizeA8 + sizeRGBA);
}
log.appendFormat("Other:\n");
log.appendFormat(" FboCache %8d / %8d\n",
@@ -272,7 +276,8 @@ void Caches::dumpMemoryUsage(String8 &log) {
total += dropShadowCache.getSize();
total += patchCache.getSize();
for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) {
- total += fontRenderer->getFontRendererSize(i);
+ total += fontRenderer->getFontRendererSize(i, GL_ALPHA);
+ total += fontRenderer->getFontRendererSize(i, GL_RGBA);
}
log.appendFormat("Total memory usage:\n");
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 79a7a934e78d..1b2f6514268b 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -21,7 +21,6 @@
#include <cutils/properties.h>
-#include <utils/Functor.h>
#include <utils/Log.h>
#ifdef ANDROID_ENABLE_RENDERSCRIPT
@@ -35,6 +34,7 @@
#include "Debug.h"
#include "Extensions.h"
#include "FontRenderer.h"
+#include "OpenGLRenderer.h"
#include "PixelBuffer.h"
#include "Rect.h"
@@ -45,6 +45,52 @@ namespace uirenderer {
#define RS_MIN_INPUT_CUTOFF 10000
///////////////////////////////////////////////////////////////////////////////
+// TextSetupFunctor
+///////////////////////////////////////////////////////////////////////////////
+status_t TextSetupFunctor::operator ()(int what, void* data) {
+ Data* typedData = reinterpret_cast<Data*>(data);
+ GLenum glyphFormat = typedData ? typedData->glyphFormat : GL_ALPHA;
+
+ renderer->setupDraw();
+ renderer->setupDrawTextGamma(paint);
+ renderer->setupDrawDirtyRegionsDisabled();
+ renderer->setupDrawWithTexture(glyphFormat == GL_ALPHA);
+ switch (glyphFormat) {
+ case GL_ALPHA: {
+ renderer->setupDrawAlpha8Color(paint->getColor(), alpha);
+ break;
+ }
+ case GL_RGBA: {
+ float floatAlpha = alpha / 255.0f;
+ renderer->setupDrawColor(floatAlpha, floatAlpha, floatAlpha, floatAlpha);
+ break;
+ }
+ default: {
+#if DEBUG_FONT_RENDERER
+ ALOGD("TextSetupFunctor: called with unknown glyph format %x", glyphFormat);
+#endif
+ break;
+ }
+ }
+ renderer->setupDrawColorFilter();
+ renderer->setupDrawShader();
+ renderer->setupDrawBlending(true, mode);
+ renderer->setupDrawProgram();
+ renderer->setupDrawModelView(x, y, x, y, pureTranslate, true);
+ // Calling setupDrawTexture with the name 0 will enable the
+ // uv attributes and increase the texture unit count
+ // texture binding will be performed by the font renderer as
+ // needed
+ renderer->setupDrawTexture(0);
+ renderer->setupDrawPureColorUniforms();
+ renderer->setupDrawColorFilterUniforms();
+ renderer->setupDrawShaderUniforms(pureTranslate);
+ renderer->setupDrawTextGammaUniforms();
+
+ return NO_ERROR;
+}
+
+///////////////////////////////////////////////////////////////////////////////
// FontRenderer
///////////////////////////////////////////////////////////////////////////////
@@ -103,11 +149,16 @@ FontRenderer::FontRenderer() :
sLogFontRendererCreate = false;
}
-FontRenderer::~FontRenderer() {
- for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
- delete mCacheTextures[i];
+void clearCacheTextures(Vector<CacheTexture*>& cacheTextures) {
+ for (uint32_t i = 0; i < cacheTextures.size(); i++) {
+ delete cacheTextures[i];
}
- mCacheTextures.clear();
+ cacheTextures.clear();
+}
+
+FontRenderer::~FontRenderer() {
+ clearCacheTextures(mACacheTextures);
+ clearCacheTextures(mRGBACacheTextures);
LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
while (it.next()) {
@@ -124,15 +175,19 @@ void FontRenderer::flushAllAndInvalidate() {
it.value()->invalidateTextureCache();
}
- for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
- mCacheTextures[i]->init();
+ for (uint32_t i = 0; i < mACacheTextures.size(); i++) {
+ mACacheTextures[i]->init();
+ }
+
+ for (uint32_t i = 0; i < mRGBACacheTextures.size(); i++) {
+ mRGBACacheTextures[i]->init();
}
}
-void FontRenderer::flushLargeCaches() {
+void FontRenderer::flushLargeCaches(Vector<CacheTexture*>& cacheTextures) {
// Start from 1; don't deallocate smallest/default texture
- for (uint32_t i = 1; i < mCacheTextures.size(); i++) {
- CacheTexture* cacheTexture = mCacheTextures[i];
+ for (uint32_t i = 1; i < cacheTextures.size(); i++) {
+ CacheTexture* cacheTexture = cacheTextures[i];
if (cacheTexture->getPixelBuffer()) {
cacheTexture->init();
LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
@@ -144,11 +199,16 @@ void FontRenderer::flushLargeCaches() {
}
}
-CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph,
- uint32_t* startX, uint32_t* startY) {
- for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
- if (mCacheTextures[i]->fitBitmap(glyph, startX, startY)) {
- return mCacheTextures[i];
+void FontRenderer::flushLargeCaches() {
+ flushLargeCaches(mACacheTextures);
+ flushLargeCaches(mRGBACacheTextures);
+}
+
+CacheTexture* FontRenderer::cacheBitmapInTexture(Vector<CacheTexture*>& cacheTextures,
+ const SkGlyph& glyph, uint32_t* startX, uint32_t* startY) {
+ for (uint32_t i = 0; i < cacheTextures.size(); i++) {
+ if (cacheTextures[i]->fitBitmap(glyph, startX, startY)) {
+ return cacheTextures[i];
}
}
// Could not fit glyph into current cache textures
@@ -169,9 +229,26 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp
cachedGlyph->mIsValid = false;
+ // choose an appropriate cache texture list for this glyph format
+ SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat);
+ Vector<CacheTexture*>* cacheTextures = NULL;
+ switch (format) {
+ case SkMask::kA8_Format:
+ cacheTextures = &mACacheTextures;
+ break;
+ case SkMask::kARGB32_Format:
+ cacheTextures = &mRGBACacheTextures;
+ break;
+ default:
+#if DEBUG_FONT_RENDERER
+ ALOGD("getCacheTexturesForFormat: unknown SkMask format %x", format);
+#endif
+ return;
+ }
+
// If the glyph is too tall, don't cache it
if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 >
- mCacheTextures[mCacheTextures.size() - 1]->getHeight()) {
+ (*cacheTextures)[cacheTextures->size() - 1]->getHeight()) {
ALOGE("Font size too large to fit in cache. width, height = %i, %i",
(int) glyph.fWidth, (int) glyph.fHeight);
return;
@@ -181,14 +258,14 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp
uint32_t startX = 0;
uint32_t startY = 0;
- CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
+ CacheTexture* cacheTexture = cacheBitmapInTexture(*cacheTextures, glyph, &startX, &startY);
if (!cacheTexture) {
if (!precaching) {
// If the new glyph didn't fit and we are not just trying to precache it,
// clear out the cache and try again
flushAllAndInvalidate();
- cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
+ cacheTexture = cacheBitmapInTexture(*cacheTextures, glyph, &startX, &startY);
}
if (!cacheTexture) {
@@ -216,24 +293,21 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp
cacheTexture->allocateMesh();
}
- // Tells us whether the glyphs is B&W (1 bit per pixel)
- // or anti-aliased (8 bits per pixel)
- SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat);
-
uint8_t* cacheBuffer = cacheTexture->getPixelBuffer()->map();
- uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
-
- // Copy the glyph image, taking the mask format into account
uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
- int stride = glyph.rowBytes();
-
- uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
- memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
+ int srcStride = glyph.rowBytes();
+ // Copy the glyph image, taking the mask format into account
switch (format) {
case SkMask::kA8_Format: {
+ uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
+ uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX
+ - TEXTURE_BORDER_SIZE;
+ // write leading border line
+ memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
+ // write glyph data
if (mGammaTable) {
- for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) {
+ for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) {
row = cacheY * cacheWidth;
cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
@@ -243,21 +317,55 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp
cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
}
} else {
- for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) {
+ for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) {
row = cacheY * cacheWidth;
memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth);
cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
}
}
+ // write trailing border line
+ row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
+ memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
+ break;
+ }
+ case SkMask::kARGB32_Format: {
+ // prep data lengths
+ const size_t formatSize = PixelBuffer::formatSize(GL_RGBA);
+ const size_t borderSize = formatSize * TEXTURE_BORDER_SIZE;
+ size_t rowSize = formatSize * glyph.fWidth;
+ // prep advances
+ size_t dstStride = formatSize * cacheWidth;
+ // prep indices
+ // - we actually start one row early, and then increment before first copy
+ uint8_t* src = &bitmapBuffer[0 - srcStride];
+ uint8_t* dst = &cacheBuffer[cacheTexture->getOffset(startX, startY - 1)];
+ uint8_t* dstEnd = &cacheBuffer[cacheTexture->getOffset(startX, endY - 1)];
+ uint8_t* dstL = dst - borderSize;
+ uint8_t* dstR = dst + rowSize;
+ // write leading border line
+ memset(dstL, 0, rowSize + 2 * borderSize);
+ // write glyph data
+ while (dst < dstEnd) {
+ memset(dstL += dstStride, 0, borderSize); // leading border column
+ memcpy(dst += dstStride, src += srcStride, rowSize); // glyph data
+ memset(dstR += dstStride, 0, borderSize); // trailing border column
+ }
+ // write trailing border line
+ memset(dstL, 0, rowSize + 2 * borderSize);
break;
}
case SkMask::kBW_Format: {
+ uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
+ uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX
+ - TEXTURE_BORDER_SIZE;
static const uint8_t COLORS[2] = { 0, 255 };
-
+ // write leading border line
+ memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
+ // write glyph data
for (cacheY = startY; cacheY < endY; cacheY++) {
cacheX = startX;
- int rowBytes = stride;
+ int rowBytes = srcStride;
uint8_t* buffer = bitmapBuffer;
row = cacheY * cacheWidth;
@@ -270,23 +378,24 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp
}
cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
- bitmapBuffer += stride;
+ bitmapBuffer += srcStride;
}
+ // write trailing border line
+ row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
+ memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
break;
}
default:
- ALOGW("Unkown glyph format: 0x%x", format);
+ ALOGW("Unknown glyph format: 0x%x", format);
break;
}
- row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
- memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
-
cachedGlyph->mIsValid = true;
}
-CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) {
- CacheTexture* cacheTexture = new CacheTexture(width, height, gMaxNumberOfQuads);
+CacheTexture* FontRenderer::createCacheTexture(int width, int height, GLenum format,
+ bool allocate) {
+ CacheTexture* cacheTexture = new CacheTexture(width, height, format, gMaxNumberOfQuads);
if (allocate) {
Caches::getInstance().activeTexture(0);
@@ -298,17 +407,23 @@ CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool alloc
}
void FontRenderer::initTextTexture() {
- for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
- delete mCacheTextures[i];
- }
- mCacheTextures.clear();
+ clearCacheTextures(mACacheTextures);
+ clearCacheTextures(mRGBACacheTextures);
mUploadTexture = false;
- mCacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true));
- mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false));
- mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false));
- mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, false));
- mCurrentCacheTexture = mCacheTextures[0];
+ mACacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight,
+ GL_ALPHA, true));
+ mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1,
+ GL_ALPHA, false));
+ mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1,
+ GL_ALPHA, false));
+ mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight,
+ GL_ALPHA, false));
+ mRGBACacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight,
+ GL_RGBA, false));
+ mRGBACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1,
+ GL_RGBA, false));
+ mCurrentCacheTexture = mACacheTextures[0];
}
// We don't want to allocate anything unless we actually draw text
@@ -322,20 +437,10 @@ void FontRenderer::checkInit() {
mInitialized = true;
}
-void FontRenderer::checkTextureUpdate() {
- if (!mUploadTexture) {
- return;
- }
-
- Caches& caches = Caches::getInstance();
- GLuint lastTextureId = 0;
-
- bool resetPixelStore = false;
- glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-
- // Iterate over all the cache textures and see which ones need to be updated
- for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
- CacheTexture* cacheTexture = mCacheTextures[i];
+void checkTextureUpdateForCache(Caches& caches, Vector<CacheTexture*>& cacheTextures,
+ bool& resetPixelStore, GLuint& lastTextureId) {
+ for (uint32_t i = 0; i < cacheTextures.size(); i++) {
+ CacheTexture* cacheTexture = cacheTextures[i];
if (cacheTexture->isDirty() && cacheTexture->getPixelBuffer()) {
if (cacheTexture->getTextureId() != lastTextureId) {
lastTextureId = cacheTexture->getTextureId();
@@ -346,13 +451,24 @@ void FontRenderer::checkTextureUpdate() {
if (cacheTexture->upload()) {
resetPixelStore = true;
}
-
-#if DEBUG_FONT_RENDERER
- ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d",
- i, x, y, width, height);
-#endif
}
}
+}
+
+void FontRenderer::checkTextureUpdate() {
+ if (!mUploadTexture) {
+ return;
+ }
+
+ Caches& caches = Caches::getInstance();
+ GLuint lastTextureId = 0;
+
+ bool resetPixelStore = false;
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+ // Iterate over all the cache textures and see which ones need to be updated
+ checkTextureUpdateForCache(caches, mACacheTextures, resetPixelStore, lastTextureId);
+ checkTextureUpdateForCache(caches, mRGBACacheTextures, resetPixelStore, lastTextureId);
// Unbind any PBO we might have used to update textures
caches.unbindPixelBuffer();
@@ -366,18 +482,18 @@ void FontRenderer::checkTextureUpdate() {
mUploadTexture = false;
}
-void FontRenderer::issueDrawCommand() {
+void FontRenderer::issueDrawCommand(Vector<CacheTexture*>& cacheTextures) {
+ Caches& caches = Caches::getInstance();
bool first = true;
bool force = false;
-
- GLuint lastId = 0;
- Caches& caches = Caches::getInstance();
-
- for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
- CacheTexture* texture = mCacheTextures[i];
+ for (uint32_t i = 0; i < cacheTextures.size(); i++) {
+ CacheTexture* texture = cacheTextures[i];
if (texture->canDraw()) {
if (first) {
- if (mFunctor) (*mFunctor)(0, NULL);
+ if (mFunctor) {
+ TextSetupFunctor::Data functorData(texture->getFormat());
+ (*mFunctor)(0, &functorData);
+ }
checkTextureUpdate();
caches.bindIndicesBuffer();
@@ -407,6 +523,11 @@ void FontRenderer::issueDrawCommand() {
texture->resetMesh();
}
}
+}
+
+void FontRenderer::issueDrawCommand() {
+ issueDrawCommand(mACacheTextures);
+ issueDrawCommand(mRGBACacheTextures);
mDrawn = true;
}
@@ -582,13 +703,13 @@ bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *t
bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text,
uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path,
- float hOffset, float vOffset, Rect* bounds) {
+ float hOffset, float vOffset, Rect* bounds, Functor* functor) {
if (!mCurrentFont) {
ALOGE("No font set");
return false;
}
- initRender(clip, bounds, NULL);
+ initRender(clip, bounds, functor);
mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
finishRender();
@@ -646,10 +767,10 @@ void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, int
delete[] scratch;
}
-uint32_t FontRenderer::getCacheSize() const {
+static uint32_t calculateCacheSize(const Vector<CacheTexture*>& cacheTextures) {
uint32_t size = 0;
- for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
- CacheTexture* cacheTexture = mCacheTextures[i];
+ for (uint32_t i = 0; i < cacheTextures.size(); i++) {
+ CacheTexture* cacheTexture = cacheTextures[i];
if (cacheTexture && cacheTexture->getPixelBuffer()) {
size += cacheTexture->getPixelBuffer()->getSize();
}
@@ -657,5 +778,19 @@ uint32_t FontRenderer::getCacheSize() const {
return size;
}
+uint32_t FontRenderer::getCacheSize(GLenum format) const {
+ switch (format) {
+ case GL_ALPHA: {
+ return calculateCacheSize(mACacheTextures);
+ }
+ case GL_RGBA: {
+ return calculateCacheSize(mRGBACacheTextures);
+ }
+ default: {
+ return 0;
+ }
+ }
+}
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index c1072ed5f80b..aca47b426f13 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_HWUI_FONT_RENDERER_H
#define ANDROID_HWUI_FONT_RENDERER_H
+#include <utils/Functor.h>
#include <utils/LruCache.h>
#include <utils/Vector.h>
#include <utils/StrongPointer.h>
@@ -46,8 +47,40 @@ class Functor;
namespace android {
namespace uirenderer {
+class OpenGLRenderer;
+
+///////////////////////////////////////////////////////////////////////////////
+// TextSetupFunctor
+///////////////////////////////////////////////////////////////////////////////
+class TextSetupFunctor: public Functor {
+public:
+ struct Data {
+ Data(GLenum glyphFormat) : glyphFormat(glyphFormat) {
+ }
+
+ GLenum glyphFormat;
+ };
+
+ TextSetupFunctor(OpenGLRenderer* renderer, float x, float y, bool pureTranslate,
+ int alpha, SkXfermode::Mode mode, SkPaint* paint): Functor(),
+ renderer(renderer), x(x), y(y), pureTranslate(pureTranslate),
+ alpha(alpha), mode(mode), paint(paint) {
+ }
+ ~TextSetupFunctor() { }
+
+ status_t operator ()(int what, void* data);
+
+ OpenGLRenderer* renderer;
+ float x;
+ float y;
+ bool pureTranslate;
+ int alpha;
+ SkXfermode::Mode mode;
+ SkPaint* paint;
+};
+
///////////////////////////////////////////////////////////////////////////////
-// Renderer
+// FontRenderer
///////////////////////////////////////////////////////////////////////////////
class FontRenderer {
@@ -55,6 +88,7 @@ public:
FontRenderer();
~FontRenderer();
+ void flushLargeCaches(Vector<CacheTexture*>& cacheTextures);
void flushLargeCaches();
void setGammaTable(const uint8_t* gammaTable) {
@@ -73,7 +107,8 @@ public:
// bounds is an out parameter
bool renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
- uint32_t len, int numGlyphs, SkPath* path, float hOffset, float vOffset, Rect* bounds);
+ uint32_t len, int numGlyphs, SkPath* path, float hOffset, float vOffset, Rect* bounds,
+ Functor* functor);
struct DropShadow {
DropShadow() { };
@@ -100,7 +135,7 @@ public:
mLinearFiltering = linearFiltering;
}
- uint32_t getCacheSize() const;
+ uint32_t getCacheSize(GLenum format) const;
private:
friend class Font;
@@ -110,10 +145,11 @@ private:
void allocateTextureMemory(CacheTexture* cacheTexture);
void deallocateTextureMemory(CacheTexture* cacheTexture);
void initTextTexture();
- CacheTexture* createCacheTexture(int width, int height, bool allocate);
+ CacheTexture* createCacheTexture(int width, int height, GLenum format, bool allocate);
void cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
uint32_t *retOriginX, uint32_t *retOriginY, bool precaching);
- CacheTexture* cacheBitmapInTexture(const SkGlyph& glyph, uint32_t* startX, uint32_t* startY);
+ CacheTexture* cacheBitmapInTexture(Vector<CacheTexture*>& cacheTextures, const SkGlyph& glyph,
+ uint32_t* startX, uint32_t* startY);
void flushAllAndInvalidate();
@@ -121,6 +157,7 @@ private:
void initRender(const Rect* clip, Rect* bounds, Functor* functor);
void finishRender();
+ void issueDrawCommand(Vector<CacheTexture*>& cacheTextures);
void issueDrawCommand();
void appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
float x2, float y2, float u2, float v2,
@@ -148,7 +185,8 @@ private:
uint32_t mLargeCacheWidth;
uint32_t mLargeCacheHeight;
- Vector<CacheTexture*> mCacheTextures;
+ Vector<CacheTexture*> mACacheTextures;
+ Vector<CacheTexture*> mRGBACacheTextures;
Font* mCurrentFont;
LruCache<Font::FontDescription, Font*> mActiveFonts;
diff --git a/libs/hwui/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h
index bbfa66d37fa7..46cfd04d9ccc 100644
--- a/libs/hwui/GammaFontRenderer.h
+++ b/libs/hwui/GammaFontRenderer.h
@@ -35,7 +35,7 @@ public:
virtual FontRenderer& getFontRenderer(const SkPaint* paint) = 0;
virtual uint32_t getFontRendererCount() const = 0;
- virtual uint32_t getFontRendererSize(uint32_t fontRenderer) const = 0;
+ virtual uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const = 0;
virtual void describe(ProgramDescription& description, const SkPaint* paint) const = 0;
virtual void setupProgram(ProgramDescription& description, Program* program) const = 0;
@@ -81,8 +81,8 @@ public:
return 1;
}
- uint32_t getFontRendererSize(uint32_t fontRenderer) const {
- return mRenderer ? mRenderer->getCacheSize() : 0;
+ uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const {
+ return mRenderer ? mRenderer->getCacheSize(format) : 0;
}
void describe(ProgramDescription& description, const SkPaint* paint) const;
@@ -128,8 +128,8 @@ public:
return 1;
}
- uint32_t getFontRendererSize(uint32_t fontRenderer) const {
- return mRenderer ? mRenderer->getCacheSize() : 0;
+ uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const {
+ return mRenderer ? mRenderer->getCacheSize(format) : 0;
}
void describe(ProgramDescription& description, const SkPaint* paint) const {
@@ -162,13 +162,13 @@ public:
return kGammaCount;
}
- uint32_t getFontRendererSize(uint32_t fontRenderer) const {
+ uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const {
if (fontRenderer >= kGammaCount) return 0;
FontRenderer* renderer = mRenderers[fontRenderer];
if (!renderer) return 0;
- return renderer->getCacheSize();
+ return renderer->getCacheSize(format);
}
void describe(ProgramDescription& description, const SkPaint* paint) const {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index bc00ce897756..be0cd2afb105 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2825,48 +2825,6 @@ bool OpenGLRenderer::canSkipText(const SkPaint* paint) const {
return alpha == 0.0f && getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode;
}
-class TextSetupFunctor: public Functor {
-public:
- TextSetupFunctor(OpenGLRenderer& renderer, float x, float y, bool pureTranslate,
- int alpha, SkXfermode::Mode mode, SkPaint* paint): Functor(),
- renderer(renderer), x(x), y(y), pureTranslate(pureTranslate),
- alpha(alpha), mode(mode), paint(paint) {
- }
- ~TextSetupFunctor() { }
-
- status_t operator ()(int what, void* data) {
- renderer.setupDraw();
- renderer.setupDrawTextGamma(paint);
- renderer.setupDrawDirtyRegionsDisabled();
- renderer.setupDrawWithTexture(true);
- renderer.setupDrawAlpha8Color(paint->getColor(), alpha);
- renderer.setupDrawColorFilter();
- renderer.setupDrawShader();
- renderer.setupDrawBlending(true, mode);
- renderer.setupDrawProgram();
- renderer.setupDrawModelView(x, y, x, y, pureTranslate, true);
- // Calling setupDrawTexture with the name 0 will enable the
- // uv attributes and increase the texture unit count
- // texture binding will be performed by the font renderer as
- // needed
- renderer.setupDrawTexture(0);
- renderer.setupDrawPureColorUniforms();
- renderer.setupDrawColorFilterUniforms();
- renderer.setupDrawShaderUniforms(pureTranslate);
- renderer.setupDrawTextGammaUniforms();
-
- return NO_ERROR;
- }
-
- OpenGLRenderer& renderer;
- float x;
- float y;
- bool pureTranslate;
- int alpha;
- SkXfermode::Mode mode;
- SkPaint* paint;
-};
-
status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count,
const float* positions, SkPaint* paint) {
if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint)) {
@@ -2912,7 +2870,7 @@ status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count
const bool hasActiveLayer = hasLayer();
- TextSetupFunctor functor(*this, x, y, pureTranslate, alpha, mode, paint);
+ TextSetupFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
if (fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y,
positions, hasActiveLayer ? &bounds : NULL, &functor)) {
if (hasActiveLayer) {
@@ -3003,7 +2961,7 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, f
Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
bool status;
- TextSetupFunctor functor(*this, x, y, pureTranslate, alpha, mode, paint);
+ TextSetupFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
// don't call issuedrawcommand, do it at end of batch
bool forceFinish = (drawOpMode != kDrawOpMode_Defer);
@@ -3045,26 +3003,7 @@ status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int co
int alpha;
SkXfermode::Mode mode;
getAlphaAndMode(paint, &alpha, &mode);
-
- setupDraw();
- setupDrawTextGamma(paint);
- setupDrawDirtyRegionsDisabled();
- setupDrawWithTexture(true);
- setupDrawAlpha8Color(paint->getColor(), alpha);
- setupDrawColorFilter();
- setupDrawShader();
- setupDrawBlending(true, mode);
- setupDrawProgram();
- setupDrawModelView(0.0f, 0.0f, 0.0f, 0.0f, false, true);
- // Calling setupDrawTexture with the name 0 will enable the
- // uv attributes and increase the texture unit count
- // texture binding will be performed by the font renderer as
- // needed
- setupDrawTexture(0);
- setupDrawPureColorUniforms();
- setupDrawColorFilterUniforms();
- setupDrawShaderUniforms(false);
- setupDrawTextGammaUniforms();
+ TextSetupFunctor functor(this, 0.0f, 0.0f, false, alpha, mode, paint);
const Rect* clip = &mSnapshot->getLocalClip();
Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
@@ -3072,7 +3011,7 @@ status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int co
const bool hasActiveLayer = hasLayer();
if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path,
- hOffset, vOffset, hasActiveLayer ? &bounds : NULL)) {
+ hOffset, vOffset, hasActiveLayer ? &bounds : NULL, &functor)) {
if (hasActiveLayer) {
currentTransform().mapRect(bounds);
dirtyLayerUnchecked(bounds, getRegion());
diff --git a/libs/hwui/PixelBuffer.h b/libs/hwui/PixelBuffer.h
index 32d541776a82..9725a6162a2c 100644
--- a/libs/hwui/PixelBuffer.h
+++ b/libs/hwui/PixelBuffer.h
@@ -112,13 +112,25 @@ public:
virtual uint8_t* getMappedPointer() const = 0;
/**
- * Upload the specified rectangle of this pixe buffer as a
+ * Upload the specified rectangle of this pixel buffer as a
* GL_TEXTURE_2D texture. Calling this method will trigger
* an unmap() if necessary.
*/
virtual void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) = 0;
/**
+ * Upload the specified rectangle of this pixel buffer as a
+ * GL_TEXTURE_2D texture. Calling this method will trigger
+ * an unmap() if necessary.
+ *
+ * This is a convenience function provided to save callers the
+ * trouble of computing the offset parameter.
+ */
+ void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
+ upload(x, y, width, height, getOffset(x, y));
+ }
+
+ /**
* Returns the width of the render buffer in pixels.
*/
uint32_t getWidth() const {
@@ -140,6 +152,13 @@ public:
}
/**
+ * Returns the offset of a pixel in this pixel buffer, in bytes.
+ */
+ uint32_t getOffset(uint32_t x, uint32_t y) const {
+ return (y * mWidth + x) * formatSize(mFormat);
+ }
+
+ /**
* Returns the number of bytes per pixel in the specified format.
*
* Supported formats:
diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp
index 5f157248bf71..55503ceff1b1 100644
--- a/libs/hwui/font/CacheTexture.cpp
+++ b/libs/hwui/font/CacheTexture.cpp
@@ -108,8 +108,8 @@ CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock* blockToRemove)
// CacheTexture
///////////////////////////////////////////////////////////////////////////////
-CacheTexture::CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCount) :
- mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height),
+CacheTexture::CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount) :
+ mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height), mFormat(format),
mLinearFiltering(false), mDirty(false), mNumGlyphs(0),
mMesh(NULL), mCurrentQuad(0), mMaxQuadCount(maxQuadCount),
mCaches(Caches::getInstance()) {
@@ -182,7 +182,7 @@ void CacheTexture::allocateMesh() {
void CacheTexture::allocateTexture() {
if (!mTexture) {
- mTexture = PixelBuffer::create(GL_ALPHA, mWidth, mHeight);
+ mTexture = PixelBuffer::create(mFormat, mWidth, mHeight);
}
if (!mTextureId) {
@@ -191,8 +191,8 @@ void CacheTexture::allocateTexture() {
mCaches.bindTexture(mTextureId);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Initialize texture dimensions
- glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mWidth, mHeight, 0,
- GL_ALPHA, GL_UNSIGNED_BYTE, 0);
+ glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
+ mFormat, GL_UNSIGNED_BYTE, 0);
const GLenum filtering = getLinearFiltering() ? GL_LINEAR : GL_NEAREST;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
@@ -217,8 +217,7 @@ bool CacheTexture::upload() {
glPixelStorei(GL_UNPACK_ROW_LENGTH, mWidth);
}
- mTexture->upload(x, y, width, height, y * mWidth + x);
-
+ mTexture->upload(x, y, width, height);
setDirty(false);
return mHasES3;
@@ -232,6 +231,30 @@ void CacheTexture::setDirty(bool dirty) {
}
bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
+ switch (glyph.fMaskFormat) {
+ case SkMask::kA8_Format:
+ if (mFormat != GL_ALPHA) {
+#if DEBUG_FONT_RENDERER
+ ALOGD("fitBitmap: kA8_Format glyph cannot fit into texture format %x", mFormat);
+#endif
+ return false;
+ }
+ break;
+ case SkMask::kARGB32_Format:
+ if (mFormat != GL_RGBA) {
+#if DEBUG_FONT_RENDERER
+ ALOGD("fitBitmap: kARGB32_Format glyph cannot fit into texture format %x", mFormat);
+#endif
+ return false;
+ }
+ break;
+ default:
+#if DEBUG_FONT_RENDERER
+ ALOGD("fitBitmap: unknown glyph format %x encountered", glyph.fMaskFormat);
+#endif
+ return false;
+ }
+
if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mHeight) {
return false;
}
diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h
index 208b1ff0acd8..028b61105c3e 100644
--- a/libs/hwui/font/CacheTexture.h
+++ b/libs/hwui/font/CacheTexture.h
@@ -24,6 +24,7 @@
#include <utils/Log.h>
#include "FontUtil.h"
+#include "../PixelBuffer.h"
#include "../Rect.h"
#include "../Vertex.h"
@@ -31,7 +32,6 @@ namespace android {
namespace uirenderer {
class Caches;
-class PixelBuffer;
/**
* CacheBlock is a node in a linked list of current free space areas in a CacheTexture.
@@ -74,7 +74,7 @@ struct CacheBlock {
class CacheTexture {
public:
- CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCount);
+ CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount);
~CacheTexture();
void reset();
@@ -100,6 +100,14 @@ public:
return mHeight;
}
+ inline GLenum getFormat() const {
+ return mFormat;
+ }
+
+ inline uint32_t getOffset(uint16_t x, uint16_t y) const {
+ return (y * mWidth + x) * PixelBuffer::formatSize(mFormat);
+ }
+
inline const Rect* getDirtyRect() const {
return &mDirtyRect;
}
@@ -173,6 +181,7 @@ private:
GLuint mTextureId;
uint16_t mWidth;
uint16_t mHeight;
+ GLenum mFormat;
bool mLinearFiltering;
bool mDirty;
uint16_t mNumGlyphs;
diff --git a/libs/hwui/font/FontUtil.h b/libs/hwui/font/FontUtil.h
index f758666c1fc8..cdcb23c488e7 100644
--- a/libs/hwui/font/FontUtil.h
+++ b/libs/hwui/font/FontUtil.h
@@ -31,6 +31,9 @@
#define DEFAULT_TEXT_LARGE_CACHE_HEIGHT 512
#define TEXTURE_BORDER_SIZE 1
+#if TEXTURE_BORDER_SIZE != 1
+# error TEXTURE_BORDER_SIZE other than 1 is not currently supported
+#endif
#define CACHE_BLOCK_ROUNDING_SIZE 4
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index bcd0398b7836..14cdbb7d4e24 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1555,6 +1555,22 @@ public class AudioManager {
/**
* @hide
+ * Checks whether the current audio focus is exclusive.
+ * @return true if the top of the audio focus stack requested focus
+ * with {@link #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}
+ */
+ public boolean isAudioFocusExclusive() {
+ IAudioService service = getService();
+ try {
+ return service.getCurrentAudioFocus() == AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in isAudioFocusExclusive()", e);
+ return false;
+ }
+ }
+
+ /**
+ * @hide
* If the stream is active locally or remotely, adjust its volume according to the enforced
* priority rules.
* Note: only AudioManager.STREAM_MUSIC is supported at the moment
@@ -1771,6 +1787,12 @@ public class AudioManager {
}
/**
+ * @hide
+ * Used to indicate no audio focus has been gained or lost.
+ */
+ public static final int AUDIOFOCUS_NONE = 0;
+
+ /**
* Used to indicate a gain of audio focus, or a request of audio focus, of unknown duration.
* @see OnAudioFocusChangeListener#onAudioFocusChange(int)
* @see #requestAudioFocus(OnAudioFocusChangeListener, int, int)
@@ -1795,6 +1817,15 @@ public class AudioManager {
*/
public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3;
/**
+ * Used to indicate a temporary request of audio focus, anticipated to last a short
+ * amount of time, during which no other applications, or system components, should play
+ * anything. Examples of exclusive and transient audio focus requests are voice
+ * memo recording and speech recognition, during which the system shouldn't play any
+ * notifications, and media playback should have paused.
+ * @see #requestAudioFocus(OnAudioFocusChangeListener, int, int)
+ */
+ public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4;
+ /**
* Used to indicate a loss of audio focus of unknown duration.
* @see OnAudioFocusChangeListener#onAudioFocusChange(int)
*/
@@ -1958,14 +1989,17 @@ public class AudioManager {
* for the playback of driving directions, or notifications sounds.
* Use {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} to indicate also that it's ok for
* the previous focus owner to keep playing if it ducks its audio output.
+ * Alternatively use {@link #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE} for a temporary request
+ * that benefits from the system not playing disruptive sounds like notifications, for
+ * usecases such as voice memo recording, or speech recognition.
* Use {@link #AUDIOFOCUS_GAIN} for a focus request of unknown duration such
* as the playback of a song or a video.
* @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED}
*/
public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) {
int status = AUDIOFOCUS_REQUEST_FAILED;
- if ((durationHint < AUDIOFOCUS_GAIN) || (durationHint > AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK))
- {
+ if ((durationHint < AUDIOFOCUS_GAIN) ||
+ (durationHint > AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {
Log.e(TAG, "Invalid duration hint, audio focus request denied");
return status;
}
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index c178ae4d03a6..290866e7ea15 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -4198,6 +4198,10 @@ public class AudioService extends IAudioService.Stub {
mMediaFocusControl.unregisterAudioFocusClient(clientId);
}
+ public int getCurrentAudioFocus() {
+ return mMediaFocusControl.getCurrentAudioFocus();
+ }
+
//==========================================================================================
// Device orientation
//==========================================================================================
diff --git a/media/java/android/media/FocusRequester.java b/media/java/android/media/FocusRequester.java
index 6b34b5671c42..9a39994bae70 100644
--- a/media/java/android/media/FocusRequester.java
+++ b/media/java/android/media/FocusRequester.java
@@ -30,16 +30,12 @@ import java.io.PrintWriter;
*/
class FocusRequester {
- /**
- * Used to indicate no audio focus has been gained or lost.
- */
- private static final int AUDIOFOCUS_NONE = 0;
-
// on purpose not using this classe's name, as it will only be used from MediaFocusControl
private static final String TAG = "MediaFocusControl";
+ private static final boolean DEBUG = false;
private AudioFocusDeathHandler mDeathHandler;
- private final IAudioFocusDispatcher mFocusDispatcher;
+ private final IAudioFocusDispatcher mFocusDispatcher; // may be null
private final IBinder mSourceRef;
private final String mClientId;
private final String mPackageName;
@@ -49,7 +45,7 @@ class FocusRequester {
*/
private final int mFocusGainRequest;
/**
- * the audio focus loss received my mFocusDispatcher, is MediaFocusControl.AUDIOFOCUS_NONE if
+ * the audio focus loss received my mFocusDispatcher, is AudioManager.AUDIOFOCUS_NONE if
* it never lost focus.
*/
private int mFocusLossReceived;
@@ -69,14 +65,10 @@ class FocusRequester {
mPackageName = pn;
mCallingUid = uid;
mFocusGainRequest = focusRequest;
- mFocusLossReceived = AUDIOFOCUS_NONE;
+ mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
}
- boolean canDispatchFocus() {
- return (mFocusDispatcher != null);
- }
-
boolean hasSameClient(String otherClient) {
try {
return mClientId.compareTo(otherClient) == 0;
@@ -113,7 +105,7 @@ class FocusRequester {
private static String focusChangeToString(int focus) {
switch(focus) {
- case AUDIOFOCUS_NONE:
+ case AudioManager.AUDIOFOCUS_NONE:
return "none";
case AudioManager.AUDIOFOCUS_GAIN:
return "GAIN";
@@ -121,6 +113,8 @@ class FocusRequester {
return "GAIN_TRANSIENT";
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
return "GAIN_TRANSIENT_MAY_DUCK";
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
+ return "GAIN_TRANSIENT_EXCLUSIVE";
case AudioManager.AUDIOFOCUS_LOSS:
return "LOSS";
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
@@ -181,23 +175,24 @@ class FocusRequester {
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS:
- case AUDIOFOCUS_NONE:
+ case AudioManager.AUDIOFOCUS_NONE:
return AudioManager.AUDIOFOCUS_LOSS;
}
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
switch(mFocusLossReceived) {
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- case AUDIOFOCUS_NONE:
+ case AudioManager.AUDIOFOCUS_NONE:
return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
case AudioManager.AUDIOFOCUS_LOSS:
return AudioManager.AUDIOFOCUS_LOSS;
}
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
switch(mFocusLossReceived) {
- case AUDIOFOCUS_NONE:
+ case AudioManager.AUDIOFOCUS_NONE:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- return AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
+ return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
case AudioManager.AUDIOFOCUS_LOSS:
@@ -205,26 +200,25 @@ class FocusRequester {
}
default:
Log.e(TAG, "focusLossForGainRequest() for invalid focus request "+ gainRequest);
- return AUDIOFOCUS_NONE;
+ return AudioManager.AUDIOFOCUS_NONE;
}
}
void handleExternalFocusGain(int focusGain) {
- try {
- int focusLoss = focusLossForGainRequest(focusGain);
- if (focusLoss != mFocusLossReceived) {
- mFocusDispatcher.dispatchAudioFocusChange(focusLoss, mClientId);
- mFocusLossReceived = focusLoss;
- }
- } catch (android.os.RemoteException e) {
- Log.e(TAG, "Failure to signal loss of focus: ", e);
- }
+ int focusLoss = focusLossForGainRequest(focusGain);
+ handleFocusLoss(focusLoss);
}
void handleFocusGain(int focusGain) {
try {
- mFocusDispatcher.dispatchAudioFocusChange(focusGain, mClientId);
- mFocusLossReceived = AUDIOFOCUS_NONE;
+ if (mFocusDispatcher != null) {
+ if (DEBUG) {
+ Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to "
+ + mClientId);
+ }
+ mFocusDispatcher.dispatchAudioFocusChange(focusGain, mClientId);
+ }
+ mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
} catch (android.os.RemoteException e) {
Log.e(TAG, "Failure to signal gain of audio focus due to: ", e);
}
@@ -232,9 +226,16 @@ class FocusRequester {
void handleFocusLoss(int focusLoss) {
try {
- mFocusDispatcher.dispatchAudioFocusChange(
- focusLoss, mClientId);
- mFocusLossReceived = focusLoss;
+ if (focusLoss != mFocusLossReceived) {
+ if (mFocusDispatcher != null) {
+ if (DEBUG) {
+ Log.v(TAG, "dispatching " + focusChangeToString(focusLoss) + " to "
+ + mClientId);
+ }
+ mFocusDispatcher.dispatchAudioFocusChange(focusLoss, mClientId);
+ }
+ mFocusLossReceived = focusLoss;
+ }
} catch (android.os.RemoteException e) {
Log.e(TAG, "Failure to signal loss of audio focus due to:", e);
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 4a1646b77438..744e32a465d4 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -119,6 +119,8 @@ interface IAudioService {
void unregisterAudioFocusClient(String clientId);
+ int getCurrentAudioFocus();
+
oneway void dispatchMediaKeyEvent(in KeyEvent keyEvent);
void dispatchMediaKeyEventUnderWakelock(in KeyEvent keyEvent);
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
index ca57b92089d7..18b5c5c00583 100644
--- a/media/java/android/media/MediaFocusControl.java
+++ b/media/java/android/media/MediaFocusControl.java
@@ -265,7 +265,7 @@ public class MediaFocusControl implements OnFinished {
*/
protected void discardAudioFocusOwner() {
synchronized(mAudioFocusLock) {
- if (!mFocusStack.empty() && mFocusStack.peek().canDispatchFocus()) {
+ if (!mFocusStack.empty()) {
// notify the current focus owner it lost focus after removing it from stack
final FocusRequester exFocusOwner = mFocusStack.pop();
exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS);
@@ -280,13 +280,26 @@ public class MediaFocusControl implements OnFinished {
private void notifyTopOfAudioFocusStack() {
// notify the top of the stack it gained focus
- if (!mFocusStack.empty() && mFocusStack.peek().canDispatchFocus()) {
+ if (!mFocusStack.empty()) {
if (canReassignAudioFocus()) {
mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);
}
}
}
+ /**
+ * Focus is requested, propagate the associated loss throughout the stack.
+ * @param focusGain the new focus gain that will later be added at the top of the stack
+ */
+ private void propagateFocusLossFromGain_syncAf(int focusGain) {
+ // going through the audio focus stack to signal new focus, traversing order doesn't
+ // matter as all entries respond to the same external focus gain
+ Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
+ while(stackIterator.hasNext()) {
+ stackIterator.next().handleExternalFocusGain(focusGain);
+ }
+ }
+
private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>();
/**
@@ -411,6 +424,15 @@ public class MediaFocusControl implements OnFinished {
}
}
+ protected int getCurrentAudioFocus() {
+ synchronized(mAudioFocusLock) {
+ if (mFocusStack.empty()) {
+ return AudioManager.AUDIOFOCUS_NONE;
+ } else {
+ return mFocusStack.peek().getGainRequest();
+ }
+ }
+ }
/** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int) */
protected int requestAudioFocus(int mainStreamType, int focusChangeHint, IBinder cb,
@@ -462,14 +484,14 @@ public class MediaFocusControl implements OnFinished {
fr.release();
}
- // notify current top of stack it is losing focus
- if (!mFocusStack.empty() && mFocusStack.peek().canDispatchFocus()) {
- mFocusStack.peek().handleExternalFocusGain(focusChangeHint);
- }
-
// focus requester might already be somewhere below in the stack, remove it
removeFocusStackEntry(clientId, false /* signal */);
+ // propagate the focus change through the stack
+ if (!mFocusStack.empty()) {
+ propagateFocusLossFromGain_syncAf(focusChangeHint);
+ }
+
// push focus requester at the top of the audio focus stack
mFocusStack.push(new FocusRequester(mainStreamType, focusChangeHint, fd, cb,
clientId, afdh, callingPackageName, Binder.getCallingUid()));
diff --git a/packages/DocumentsUI/Android.mk b/packages/DocumentsUI/Android.mk
index 1e458070591c..853353d45fff 100644
--- a/packages/DocumentsUI/Android.mk
+++ b/packages/DocumentsUI/Android.mk
@@ -5,6 +5,8 @@ LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+
LOCAL_PACKAGE_NAME := DocumentsUI
LOCAL_CERTIFICATE := platform
diff --git a/packages/DocumentsUI/res/drawable-hdpi/drawer_shadow.9.png b/packages/DocumentsUI/res/drawable-hdpi/drawer_shadow.9.png
new file mode 100644
index 000000000000..224cc4ff43a2
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/drawer_shadow.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_drawer.png b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer.png
new file mode 100644
index 000000000000..ff7b1def9ac3
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_search.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_search.png
new file mode 100644
index 000000000000..cc661e3ae1f9
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_search.png
Binary files differ
diff --git a/packages/DocumentsUI/res/layout/activity.xml b/packages/DocumentsUI/res/layout/activity.xml
index f96d459bf1ce..eb6d803d9e87 100644
--- a/packages/DocumentsUI/res/layout/activity.xml
+++ b/packages/DocumentsUI/res/layout/activity.xml
@@ -14,20 +14,34 @@
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/drawer_layout"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
+ android:layout_height="match_parent">
- <FrameLayout
- android:id="@+id/directory"
+ <LinearLayout
android:layout_width="match_parent"
- android:layout_height="0dip"
- android:layout_weight="1" />
+ android:layout_height="match_parent"
+ android:orientation="vertical">
- <FrameLayout
- android:id="@+id/save"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+ <FrameLayout
+ android:id="@+id/directory"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1" />
+
+ <FrameLayout
+ android:id="@+id/save"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <ListView
+ android:id="@+id/roots_list"
+ android:layout_width="300dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:background="#fff" />
-</LinearLayout>
+</android.support.v4.widget.DrawerLayout>
diff --git a/packages/DocumentsUI/res/layout/dialog_create_dir.xml b/packages/DocumentsUI/res/layout/dialog_create_dir.xml
new file mode 100644
index 000000000000..54e26b40dfdf
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/dialog_create_dir.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="?android:attr/listPreferredItemPaddingEnd">
+
+ <EditText
+ android:id="@android:id/text1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+</FrameLayout>
diff --git a/packages/DocumentsUI/res/layout/item_backend.xml b/packages/DocumentsUI/res/layout/item_backend.xml
deleted file mode 100644
index 6ec7566685a7..000000000000
--- a/packages/DocumentsUI/res/layout/item_backend.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingBottom="?android:attr/listPreferredItemPaddingEnd"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
-
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@color/chip"
- android:foreground="?android:attr/selectableItemBackground"
- android:duplicateParentState="true">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="8dp"
- android:orientation="horizontal">
-
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="24dip"
- android:layout_height="24dip"
- android:layout_marginEnd="8dp"
- android:scaleType="centerInside"
- android:contentDescription="@null" />
-
- <TextView
- android:id="@android:id/text1"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textAlignment="viewStart" />
-
- </LinearLayout>
-
- </FrameLayout>
-
-</FrameLayout>
diff --git a/packages/DocumentsUI/res/layout/item_root.xml b/packages/DocumentsUI/res/layout/item_root.xml
new file mode 100644
index 000000000000..e9cf3aaab730
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/item_root.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="@android:dimen/app_icon_size"
+ android:layout_height="@android:dimen/app_icon_size"
+ android:layout_rowSpan="2"
+ android:layout_marginEnd="8dip"
+ android:scaleType="centerInside"
+ android:contentDescription="@null" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textAlignment="viewStart" />
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAlignment="viewStart" />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/DocumentsUI/res/layout/item_title.xml b/packages/DocumentsUI/res/layout/item_title.xml
new file mode 100644
index 000000000000..fe6c14d8c6ab
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/item_title.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textAlignment="viewStart" />
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAlignment="viewStart" />
+
+</LinearLayout>
diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml
index bf7c161a83ce..a0d03b251699 100644
--- a/packages/DocumentsUI/res/menu/activity.xml
+++ b/packages/DocumentsUI/res/menu/activity.xml
@@ -19,5 +19,12 @@
android:id="@+id/menu_create_dir"
android:title="@string/menu_create_dir"
android:icon="@drawable/ic_menu_create_dir"
- android:showAsAction="always" />
+ android:showAsAction="ifRoom" />
+ <item
+ android:id="@+id/menu_search"
+ android:title="@string/menu_search"
+ android:icon="@drawable/ic_menu_search"
+ android:showAsAction="always|collapseActionView"
+ android:actionViewClass="android.widget.SearchView"
+ android:imeOptions="actionSearch" />
</menu>
diff --git a/packages/DocumentsUI/res/menu/directory.xml b/packages/DocumentsUI/res/menu/directory.xml
index c1fa2287ca16..12d0324fb26e 100644
--- a/packages/DocumentsUI/res/menu/directory.xml
+++ b/packages/DocumentsUI/res/menu/directory.xml
@@ -25,9 +25,4 @@
android:title="@string/menu_list"
android:icon="@drawable/ic_menu_list"
android:showAsAction="ifRoom" />
- <item
- android:id="@+id/menu_sort"
- android:title="@string/menu_sort"
- android:icon="@drawable/ic_menu_sort"
- android:showAsAction="ifRoom" />
</menu>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 6ae2d1212eb8..18e486dc9519 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -17,20 +17,24 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label">Documents</string>
- <string name="title_open">Open</string>
- <string name="title_save">Save</string>
+ <string name="title_open">Open from</string>
+ <string name="title_save">Save to</string>
<string name="menu_create_dir">Create folder</string>
<string name="menu_grid">Grid view</string>
<string name="menu_list">List view</string>
<string name="menu_sort">Sort by</string>
+ <string name="menu_search">Search</string>
<string name="menu_open">Open</string>
<string name="menu_save">Save</string>
<string name="mode_selected_count"><xliff:g id="count" example="3">%1$d</xliff:g> selected</string>
- <string name="sort_name">Name</string>
- <string name="sort_date">Date modified</string>
+ <string name="sort_name">By name</string>
+ <string name="sort_date">By date modified</string>
+
+ <string name="drawer_open">Open navigation drawer</string>
+ <string name="drawer_close">Close navigation drawer</string>
</resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java b/packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java
deleted file mode 100644
index 2980e2338a33..000000000000
--- a/packages/DocumentsUI/src/com/android/documentsui/BackendFragment.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2013 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.documentsui;
-
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
-import android.os.Bundle;
-import android.provider.DocumentsContract;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ArrayAdapter;
-import android.widget.GridView;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.google.android.collect.Lists;
-
-import java.util.List;
-
-/**
- * Display all known storage backends.
- */
-public class BackendFragment extends Fragment {
-
- // TODO: handle multiple accounts from single backend
-
- private GridView mGridView;
- private BackendAdapter mAdapter;
-
- public static void show(FragmentManager fm) {
- final BackendFragment fragment = new BackendFragment();
-
- final FragmentTransaction ft = fm.beginTransaction();
- ft.replace(R.id.directory, fragment);
- ft.setBreadCrumbTitle("TOP");
- ft.commitAllowingStateLoss();
- }
-
- @Override
- public View onCreateView(
- LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- final Context context = inflater.getContext();
-
- // Gather known storage providers
- final List<ProviderInfo> providers = context.getPackageManager()
- .queryContentProviders(null, -1, PackageManager.GET_META_DATA);
- final List<ProviderInfo> backends = Lists.newArrayList();
- for (ProviderInfo info : providers) {
- if (info.metaData != null
- && info.metaData.containsKey(DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) {
- backends.add(info);
- }
- }
-
- final View view = inflater.inflate(R.layout.fragment_backend, container, false);
-
- mGridView = (GridView) view.findViewById(R.id.grid);
- mGridView.setOnItemClickListener(mItemListener);
-
- mAdapter = new BackendAdapter(context, backends);
- mGridView.setAdapter(mAdapter);
- mGridView.setNumColumns(GridView.AUTO_FIT);
-
- return view;
- }
-
- private OnItemClickListener mItemListener = new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- final ProviderInfo info = mAdapter.getItem(position);
- ((DocumentsActivity) getActivity()).onBackendPicked(info);
- }
- };
-
- public static class BackendAdapter extends ArrayAdapter<ProviderInfo> {
- public BackendAdapter(Context context, List<ProviderInfo> list) {
- super(context, android.R.layout.simple_list_item_1, list);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_backend, parent, false);
- }
-
- final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
- final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
-
- final PackageManager pm = parent.getContext().getPackageManager();
- final ProviderInfo info = getItem(position);
- icon.setImageDrawable(info.loadIcon(pm));
- text1.setText(info.loadLabel(pm));
-
- return convertView;
- }
- }
-}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 531eaf3c7863..8b3dd99179c3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -16,17 +16,12 @@
package com.android.documentsui;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Context;
import android.content.CursorLoader;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
@@ -65,8 +60,6 @@ public class DirectoryFragment extends Fragment {
// TODO: show storage backend in item views when requested
- private static final String TAG_SORT = "sort";
-
private ListView mListView;
private GridView mGridView;
@@ -81,7 +74,8 @@ public class DirectoryFragment extends Fragment {
private static final int LOADER_DOCUMENTS = 2;
- public static void show(FragmentManager fm, Uri uri, String displayName) {
+ public static void show(
+ FragmentManager fm, Uri uri, String displayName, boolean addToBackStack) {
final Bundle args = new Bundle();
args.putParcelable(EXTRA_URI, uri);
@@ -90,7 +84,9 @@ public class DirectoryFragment extends Fragment {
final FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.directory, fragment);
- ft.addToBackStack(displayName);
+ if (addToBackStack) {
+ ft.addToBackStack(displayName);
+ }
ft.setBreadCrumbTitle(displayName);
ft.commitAllowingStateLoss();
}
@@ -136,7 +132,13 @@ public class DirectoryFragment extends Fragment {
sortOrder = null;
}
- final Uri contentsUri = DocumentsContract.buildContentsUri(uri);
+ final Uri contentsUri;
+ if (uri.getQueryParameter(DocumentsContract.PARAM_QUERY) != null) {
+ contentsUri = uri;
+ } else {
+ contentsUri = DocumentsContract.buildContentsUri(uri);
+ }
+
return new CursorLoader(context, contentsUri, null, null, null, sortOrder);
}
@@ -198,9 +200,6 @@ public class DirectoryFragment extends Fragment {
updateMode();
getFragmentManager().invalidateOptionsMenu();
return true;
- } else if (id == R.id.menu_sort) {
- SortFragment.show(this);
- return true;
} else {
return super.onOptionsItemSelected(item);
}
@@ -238,7 +237,7 @@ public class DirectoryFragment extends Fragment {
}
}
- private void updateSortBy() {
+ public void updateSortBy() {
getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
}
@@ -358,38 +357,6 @@ public class DirectoryFragment extends Fragment {
}
}
- public static class SortFragment extends DialogFragment {
- public static void show(DirectoryFragment parent) {
- if (!parent.isAdded()) return;
-
- final SortFragment dialog = new SortFragment();
- dialog.setTargetFragment(parent, 0);
- dialog.show(parent.getFragmentManager(), TAG_SORT);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final Context context = getActivity();
- final DisplayState state = getDisplayState(this);
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(R.string.menu_sort);
- builder.setSingleChoiceItems(new CharSequence[] {
- getText(R.string.sort_name),
- getText(R.string.sort_date),
- }, state.sortBy, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- state.sortBy = which;
- ((DirectoryFragment) getTargetFragment()).updateSortBy();
- dismiss();
- }
- });
-
- return builder.create();
- }
- }
-
private static int getDocumentFlags(Context context, Uri uri) {
final Cursor cursor = context.getContentResolver().query(uri, new String[] {
DocumentColumns.FLAGS }, null, null, null);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index c45d2b4990ff..13def57e5931 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -21,31 +21,55 @@ import static com.android.documentsui.DirectoryFragment.getCursorString;
import android.app.ActionBar;
import android.app.ActionBar.OnNavigationListener;
import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
import android.app.FragmentManager;
-import android.app.FragmentManager.BackStackEntry;
import android.app.FragmentManager.OnBackStackChangedListener;
import android.content.ClipData;
import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
+import android.content.res.Resources.NotFoundException;
import android.database.Cursor;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.DocumentColumns;
+import android.provider.DocumentsContract.RootColumns;
+import android.support.v4.app.ActionBarDrawerToggle;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v4.widget.DrawerLayout.DrawerListener;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SearchView;
+import android.widget.SearchView.OnQueryTextListener;
import android.widget.TextView;
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -55,18 +79,30 @@ public class DocumentsActivity extends Activity {
// TODO: fragment to show recently opened documents
// TODO: pull actionbar icon from current backend
+ private static final String TAG_CREATE_DIRECTORY = "create_directory";
+
private static final int ACTION_OPEN = 1;
private static final int ACTION_CREATE = 2;
private int mAction;
private String[] mAcceptMimes;
+ private SearchView mSearchView;
+
+ private DrawerLayout mDrawerLayout;
+ private ActionBarDrawerToggle mDrawerToggle;
+
+ private ArrayList<Root> mRoots = Lists.newArrayList();
+ private RootsAdapter mRootsAdapter;
+ private ListView mRootsList;
+
private final DisplayState mDisplayState = new DisplayState();
- private boolean mIgnoreNextNavigation;
+ private Root mCurrentRoot;
private Uri mCurrentDir;
private boolean mCurrentSupportsCreate;
+ private boolean mCurrentSupportsSearch;
@Override
public void onCreate(Bundle icicle) {
@@ -98,40 +134,91 @@ public class DocumentsActivity extends Activity {
setContentView(R.layout.activity);
getFragmentManager().addOnBackStackChangedListener(mStackListener);
- BackendFragment.show(getFragmentManager());
-
- updateActionBar();
if (mAction == ACTION_CREATE) {
final String mimeType = getIntent().getType();
final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
SaveFragment.show(getFragmentManager(), mimeType, title);
}
+
+ mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+ mRootsAdapter = new RootsAdapter(this, mRoots);
+ mRootsList = (ListView) findViewById(R.id.roots_list);
+ mRootsList.setAdapter(mRootsAdapter);
+ mRootsList.setOnItemClickListener(mRootsListener);
+
+ mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
+ R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
+
+ mDrawerLayout.setDrawerListener(mDrawerListener);
+ mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
+
+ mDrawerLayout.openDrawer(mRootsList);
+
+ updateActionBar();
+ updateRoots();
+ }
+
+ private DrawerListener mDrawerListener = new DrawerListener() {
+ @Override
+ public void onDrawerSlide(View drawerView, float slideOffset) {
+ mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
+ }
+
+ @Override
+ public void onDrawerOpened(View drawerView) {
+ mDrawerToggle.onDrawerOpened(drawerView);
+ updateActionBar();
+ }
+
+ @Override
+ public void onDrawerClosed(View drawerView) {
+ mDrawerToggle.onDrawerClosed(drawerView);
+ updateActionBar();
+ }
+
+ @Override
+ public void onDrawerStateChanged(int newState) {
+ mDrawerToggle.onDrawerStateChanged(newState);
+ }
+ };
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ mDrawerToggle.syncState();
}
public void updateActionBar() {
final FragmentManager fm = getFragmentManager();
final ActionBar actionBar = getActionBar();
- if (fm.getBackStackEntryCount() > 0) {
- actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
- actionBar.setDisplayShowHomeEnabled(true);
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setTitle(null);
- actionBar.setListNavigationCallbacks(mStackAdapter, mNavigationListener);
- actionBar.setSelectedNavigationItem(mStackAdapter.getCount() - 1);
- mIgnoreNextNavigation = true;
+ actionBar.setDisplayShowHomeEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(true);
- } else {
+ if (mDrawerLayout.isDrawerOpen(mRootsList)) {
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
- actionBar.setDisplayShowHomeEnabled(false);
- actionBar.setDisplayHomeAsUpEnabled(false);
+ actionBar.setIcon(new ColorDrawable());
if (mAction == ACTION_OPEN) {
actionBar.setTitle(R.string.title_open);
} else if (mAction == ACTION_CREATE) {
actionBar.setTitle(R.string.title_save);
}
+
+ } else {
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
+ if (mCurrentRoot != null) {
+ actionBar.setIcon(mCurrentRoot.icon);
+ }
+ actionBar.setTitle(null);
+ actionBar.setListNavigationCallbacks(mSortAdapter, mSortListener);
+
+ if (fm.getBackStackEntryCount() > 0) {
+ mDrawerToggle.setDrawerIndicatorEnabled(false);
+ } else {
+ mDrawerToggle.setDrawerIndicatorEnabled(true);
+ }
}
}
@@ -139,6 +226,25 @@ public class DocumentsActivity extends Activity {
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.activity, menu);
+
+ final MenuItem searchMenu = menu.findItem(R.id.menu_search);
+ mSearchView = (SearchView) searchMenu.getActionView();
+ mSearchView.setOnQueryTextListener(new OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ // TODO: clear existing directory stack?
+ final Uri searchUri = DocumentsContract.buildSearchUri(mCurrentDir, query);
+ DirectoryFragment.show(getFragmentManager(), searchUri, query, true);
+ mSearchView.setIconified(true);
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ return false;
+ }
+ });
+
return true;
}
@@ -150,17 +256,29 @@ public class DocumentsActivity extends Activity {
createDir.setVisible(mAction == ACTION_CREATE);
createDir.setEnabled(mCurrentSupportsCreate);
+ // TODO: close any search in-progress when hiding
+ final MenuItem search = menu.findItem(R.id.menu_search);
+ search.setVisible(mCurrentSupportsSearch);
+
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
+ if (mDrawerToggle.onOptionsItemSelected(item)) {
+ return true;
+ }
+
final int id = item.getItemId();
if (id == android.R.id.home) {
getFragmentManager().popBackStack();
updateActionBar();
+ return true;
} else if (id == R.id.menu_create_dir) {
- // TODO: show dialog to create directory
+ CreateDirectoryFragment.show(getFragmentManager());
+ return true;
+ } else if (id == R.id.menu_search) {
+ return false;
}
return super.onOptionsItemSelected(item);
}
@@ -172,46 +290,73 @@ public class DocumentsActivity extends Activity {
}
};
- private BaseAdapter mStackAdapter = new BaseAdapter() {
+ // TODO: support additional sort orders
+ private BaseAdapter mSortAdapter = new BaseAdapter() {
@Override
public int getCount() {
- return getFragmentManager().getBackStackEntryCount();
+ return 2;
}
@Override
public Object getItem(int position) {
- return getFragmentManager().getBackStackEntryAt(position);
+ switch (position) {
+ case 0:
+ return getText(R.string.sort_name);
+ case 1:
+ return getText(R.string.sort_date);
+ default:
+ return null;
+ }
}
@Override
public long getItemId(int position) {
- return getFragmentManager().getBackStackEntryAt(position).getId();
+ return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_title, parent, false);
+ }
+
+ final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+ final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
+
+ final FragmentManager fm = getFragmentManager();
+ final int count = fm.getBackStackEntryCount();
+ if (count > 0) {
+ title.setText(fm.getBackStackEntryAt(count - 1).getBreadCrumbTitle());
+ } else if (mCurrentRoot != null) {
+ title.setText(mCurrentRoot.title);
+ } else {
+ title.setText(null);
+ }
+
+ summary.setText((String) getItem(position));
+
+ return convertView;
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(parent.getContext())
.inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
}
- final BackStackEntry entry = getFragmentManager().getBackStackEntryAt(position);
final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
- text1.setText(entry.getBreadCrumbTitle());
+ text1.setText((String) getItem(position));
return convertView;
}
};
- private OnNavigationListener mNavigationListener = new OnNavigationListener() {
+ private OnNavigationListener mSortListener = new OnNavigationListener() {
@Override
public boolean onNavigationItemSelected(int itemPosition, long itemId) {
- if (mIgnoreNextNavigation) {
- mIgnoreNextNavigation = false;
- return false;
- }
-
- getFragmentManager().popBackStack((int) itemId, 0);
+ // TODO: request updated sort order
return true;
}
};
@@ -223,6 +368,7 @@ public class DocumentsActivity extends Activity {
public void onDirectoryChanged(Uri uri, int flags) {
mCurrentDir = uri;
mCurrentSupportsCreate = (flags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0;
+ mCurrentSupportsSearch = (flags & DocumentsContract.FLAG_SUPPORTS_SEARCH) != 0;
if (mAction == ACTION_CREATE) {
final FragmentManager fm = getFragmentManager();
@@ -232,18 +378,11 @@ public class DocumentsActivity extends Activity {
invalidateOptionsMenu();
}
- public void onBackendPicked(ProviderInfo info) {
- final Uri uri = DocumentsContract.buildDocumentUri(
- info.authority, DocumentsContract.ROOT_GUID);
- final CharSequence displayName = info.loadLabel(getPackageManager());
- DirectoryFragment.show(getFragmentManager(), uri, displayName.toString());
- }
-
public void onDocumentPicked(Document doc) {
final FragmentManager fm = getFragmentManager();
if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(doc.mimeType)) {
// Nested directory picked, recurse using new fragment
- DirectoryFragment.show(fm, doc.uri, doc.displayName);
+ DirectoryFragment.show(fm, doc.uri, doc.displayName, true);
} else if (mAction == ACTION_OPEN) {
// Explicit file picked, return
onFinished(doc.uri);
@@ -263,8 +402,12 @@ public class DocumentsActivity extends Activity {
}
public void onSaveRequested(String mimeType, String displayName) {
- // TODO: create file, confirming before overwriting
- final Uri uri = null;
+ final ContentValues values = new ContentValues();
+ values.put(DocumentColumns.MIME_TYPE, mimeType);
+ values.put(DocumentColumns.DISPLAY_NAME, displayName);
+
+ // TODO: handle errors from remote side
+ final Uri uri = getContentResolver().insert(mCurrentDir, values);
onFinished(uri);
}
@@ -283,11 +426,10 @@ public class DocumentsActivity extends Activity {
intent.setClipData(clipData);
}
- intent.addFlags(
- Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION);
- if (mAction == ACTION_CREATE) {
- intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- }
+ // TODO: omit WRITE and PERSIST for GET_CONTENT
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION);
setResult(Activity.RESULT_OK, intent);
finish();
@@ -305,6 +447,46 @@ public class DocumentsActivity extends Activity {
public static final int SORT_BY_DATE = 1;
}
+ public static class Root {
+ public int rootType;
+ public Uri uri;
+ public Drawable icon;
+ public String title;
+ public String summary;
+
+ public static Root fromCursor(Context context, ProviderInfo info, Cursor cursor) {
+ final Root root = new Root();
+
+ root.rootType = cursor.getInt(cursor.getColumnIndex(RootColumns.ROOT_TYPE));
+ root.uri = DocumentsContract.buildDocumentUri(
+ info.authority, cursor.getString(cursor.getColumnIndex(RootColumns.GUID)));
+
+ final PackageManager pm = context.getPackageManager();
+ final int icon = cursor.getInt(cursor.getColumnIndex(RootColumns.ICON));
+ if (icon != 0) {
+ try {
+ root.icon = pm.getResourcesForApplication(info.applicationInfo)
+ .getDrawable(icon);
+ } catch (NotFoundException e) {
+ throw new RuntimeException(e);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ root.icon = info.loadIcon(pm);
+ }
+
+ root.title = cursor.getString(cursor.getColumnIndex(RootColumns.TITLE));
+ if (root.title == null) {
+ root.title = info.loadLabel(pm).toString();
+ }
+
+ root.summary = cursor.getString(cursor.getColumnIndex(RootColumns.SUMMARY));
+
+ return root;
+ }
+ }
+
public static class Document {
public Uri uri;
public String mimeType;
@@ -318,6 +500,24 @@ public class DocumentsActivity extends Activity {
doc.displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
return doc;
}
+
+ public static Document fromUri(ContentResolver resolver, Uri uri) {
+ final Document doc = new Document();
+ doc.uri = uri;
+
+ final Cursor cursor = resolver.query(uri, null, null, null, null);
+ try {
+ if (!cursor.moveToFirst()) {
+ throw new IllegalArgumentException("Missing details for " + uri);
+ }
+ doc.mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
+ doc.displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
+ } finally {
+ cursor.close();
+ }
+
+ return doc;
+ }
}
public static boolean mimeMatches(String filter, String[] tests) {
@@ -359,4 +559,117 @@ public class DocumentsActivity extends Activity {
}
}
}
+
+ /**
+ * Gather roots from all known storage providers.
+ */
+ private void updateRoots() {
+ mRoots.clear();
+
+ final List<ProviderInfo> providers = getPackageManager()
+ .queryContentProviders(null, -1, PackageManager.GET_META_DATA);
+ for (ProviderInfo info : providers) {
+ if (info.metaData != null
+ && info.metaData.containsKey(DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) {
+ // TODO: populate roots on background thread, and cache results
+ final Uri uri = DocumentsContract.buildRootsUri(info.authority);
+ final Cursor cursor = getContentResolver().query(uri, null, null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ mRoots.add(Root.fromCursor(this, info, cursor));
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+ }
+
+ private OnItemClickListener mRootsListener = new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ // Clear entire backstack and start in new root
+ final FragmentManager fm = getFragmentManager();
+ while (fm.getBackStackEntryCount() > 0) {
+ fm.popBackStackImmediate();
+ }
+
+ mCurrentRoot = mRootsAdapter.getItem(position);
+ DirectoryFragment.show(
+ getFragmentManager(), mCurrentRoot.uri, mCurrentRoot.title, false);
+
+ mDrawerLayout.closeDrawers();
+ }
+ };
+
+ public static class RootsAdapter extends ArrayAdapter<Root> {
+ public RootsAdapter(Context context, List<Root> list) {
+ super(context, android.R.layout.simple_list_item_1, list);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_root, parent, false);
+ }
+
+ final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
+ final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+ final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
+
+ final Root root = getItem(position);
+ icon.setImageDrawable(root.icon);
+ title.setText(root.title);
+
+ summary.setText(root.summary);
+ summary.setVisibility(root.summary != null ? View.VISIBLE : View.GONE);
+
+ return convertView;
+ }
+ }
+
+ public static class CreateDirectoryFragment extends DialogFragment {
+ public static void show(FragmentManager fm) {
+ final CreateDirectoryFragment dialog = new CreateDirectoryFragment();
+ dialog.show(fm, TAG_CREATE_DIRECTORY);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+ final ContentResolver resolver = context.getContentResolver();
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
+
+ final View view = dialogInflater.inflate(R.layout.dialog_create_dir, null, false);
+ final EditText text1 = (EditText)view.findViewById(android.R.id.text1);
+
+ builder.setTitle(R.string.menu_create_dir);
+ builder.setView(view);
+
+ builder.setPositiveButton(android.R.string.ok, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final String displayName = text1.getText().toString();
+
+ final ContentValues values = new ContentValues();
+ values.put(DocumentColumns.MIME_TYPE, DocumentsContract.MIME_TYPE_DIRECTORY);
+ values.put(DocumentColumns.DISPLAY_NAME, displayName);
+
+ // TODO: handle errors from remote side
+ final DocumentsActivity activity = (DocumentsActivity) getActivity();
+ final Uri uri = resolver.insert(activity.mCurrentDir, values);
+
+ // Navigate into newly created child
+ final Document doc = Document.fromUri(resolver, uri);
+ activity.onDocumentPicked(doc);
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+
+ return builder.create();
+ }
+ }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
index 19a1d2a45baa..a2a4f7cb285f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
@@ -82,7 +82,7 @@ public class SaveFragment extends Fragment {
@Override
public void onClick(View v) {
final String mimeType = getArguments().getString(EXTRA_MIME_TYPE);
- final String displayName = getArguments().getString(EXTRA_DISPLAY_NAME);
+ final String displayName = mDisplayName.getText().toString();
((DocumentsActivity) getActivity()).onSaveRequested(mimeType, displayName);
}
};
diff --git a/packages/ExternalStorageProvider/res/values/strings.xml b/packages/ExternalStorageProvider/res/values/strings.xml
index 4374cfc6e7b4..0eaf500a03dd 100644
--- a/packages/ExternalStorageProvider/res/values/strings.xml
+++ b/packages/ExternalStorageProvider/res/values/strings.xml
@@ -16,4 +16,5 @@
<resources>
<string name="app_label">External Storage</string>
+ <string name="root_internal_storage">Internal storage</string>
</resources>
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index f75e3bd9b3e8..bf5811b682e8 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -28,14 +28,17 @@ import android.os.ParcelFileDescriptor;
import android.provider.BaseColumns;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.DocumentColumns;
+import android.provider.DocumentsContract.RootColumns;
+import android.util.Log;
import android.webkit.MimeTypeMap;
-import com.android.internal.annotations.GuardedBy;
-import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
import java.io.File;
import java.io.FileNotFoundException;
-import java.util.ArrayList;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.LinkedList;
public class ExternalStorageProvider extends ContentProvider {
private static final String TAG = "ExternalStorage";
@@ -44,33 +47,65 @@ public class ExternalStorageProvider extends ContentProvider {
// TODO: support searching
// TODO: support multiple storage devices
- // TODO: persist GUIDs across launches
private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- private static final int URI_DOCS_ID = 1;
- private static final int URI_DOCS_ID_CONTENTS = 2;
- private static final int URI_SEARCH = 3;
+ private static final int URI_ROOTS = 1;
+ private static final int URI_DOCS_ID = 2;
+ private static final int URI_DOCS_ID_CONTENTS = 3;
+ private static final int URI_DOCS_ID_SEARCH = 4;
- static {
- sMatcher.addURI(AUTHORITY, "docs/#", URI_DOCS_ID);
- sMatcher.addURI(AUTHORITY, "docs/#/contents", URI_DOCS_ID_CONTENTS);
- sMatcher.addURI(AUTHORITY, "search", URI_SEARCH);
+ private HashMap<String, Root> mRoots = Maps.newHashMap();
+
+ private static class Root {
+ public int rootType;
+ public String name;
+ public int icon = 0;
+ public String title = null;
+ public String summary = null;
+ public File path;
}
- @GuardedBy("mFiles")
- private ArrayList<File> mFiles = Lists.newArrayList();
+ static {
+ sMatcher.addURI(AUTHORITY, "roots", URI_ROOTS);
+ sMatcher.addURI(AUTHORITY, "docs/*", URI_DOCS_ID);
+ sMatcher.addURI(AUTHORITY, "docs/*/contents", URI_DOCS_ID_CONTENTS);
+ sMatcher.addURI(AUTHORITY, "docs/*/search", URI_DOCS_ID_SEARCH);
+ }
@Override
public boolean onCreate() {
- mFiles.clear();
- mFiles.add(Environment.getExternalStorageDirectory());
+ mRoots.clear();
+
+ final Root root = new Root();
+ root.rootType = DocumentsContract.ROOT_TYPE_DEVICE_ADVANCED;
+ root.name = "internal";
+ root.title = getContext().getString(R.string.root_internal_storage);
+ root.path = Environment.getExternalStorageDirectory();
+ mRoots.put(root.name, root);
+
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ final int match = sMatcher.match(uri);
+ if (match == URI_ROOTS) {
+ // TODO: support custom projections
+ projection = new String[] {
+ RootColumns.ROOT_TYPE, RootColumns.GUID, RootColumns.ICON, RootColumns.TITLE,
+ RootColumns.SUMMARY, RootColumns.AVAILABLE_BYTES };
+
+ final MatrixCursor cursor = new MatrixCursor(projection);
+ for (Root root : mRoots.values()) {
+ final String guid = fileToGuid(root.path);
+ cursor.addRow(new Object[] {
+ root.rootType, guid, root.icon, root.title, root.summary,
+ root.path.getFreeSpace() });
+ }
+ return cursor;
+ }
// TODO: support custom projections
projection = new String[] {
@@ -79,21 +114,37 @@ public class ExternalStorageProvider extends ContentProvider {
DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS };
final MatrixCursor cursor = new MatrixCursor(projection);
- switch (sMatcher.match(uri)) {
+ switch (match) {
case URI_DOCS_ID: {
- final int id = Integer.parseInt(uri.getPathSegments().get(1));
- synchronized (mFiles) {
- includeFileLocked(cursor, id);
- }
+ final String guid = uri.getPathSegments().get(1);
+ includeFile(cursor, guid);
break;
}
case URI_DOCS_ID_CONTENTS: {
- final int parentId = Integer.parseInt(uri.getPathSegments().get(1));
- synchronized (mFiles) {
- final File parent = mFiles.get(parentId);
- for (File file : parent.listFiles()) {
- final int id = findOrCreateFileLocked(file);
- includeFileLocked(cursor, id);
+ final String guid = uri.getPathSegments().get(1);
+ final File parent = guidToFile(guid);
+ for (File file : parent.listFiles()) {
+ includeFile(cursor, fileToGuid(file));
+ }
+ break;
+ }
+ case URI_DOCS_ID_SEARCH: {
+ final String guid = uri.getPathSegments().get(1);
+ final File parent = guidToFile(guid);
+ final String query = uri.getQueryParameter(DocumentsContract.PARAM_QUERY).toLowerCase();
+
+ final LinkedList<File> pending = new LinkedList<File>();
+ pending.add(parent);
+ while (!pending.isEmpty() && cursor.getCount() < 20) {
+ final File file = pending.removeFirst();
+ if (file.isDirectory()) {
+ for (File child : file.listFiles()) {
+ pending.add(child);
+ }
+ } else {
+ if (file.getName().toLowerCase().contains(query)) {
+ includeFile(cursor, fileToGuid(file));
+ }
}
}
break;
@@ -107,43 +158,61 @@ public class ExternalStorageProvider extends ContentProvider {
return cursor;
}
- private int findOrCreateFileLocked(File file) {
- int id = mFiles.indexOf(file);
- if (id == -1) {
- id = mFiles.size();
- mFiles.add(file);
+ private String fileToGuid(File file) {
+ final String path = file.getAbsolutePath();
+ for (Root root : mRoots.values()) {
+ final String rootPath = root.path.getAbsolutePath();
+ if (path.startsWith(rootPath)) {
+ return root.name + ':' + Uri.encode(path.substring(rootPath.length()));
+ }
}
- return id;
+
+ throw new IllegalArgumentException("Failed to find root for " + file);
+ }
+
+ private File guidToFile(String guid) {
+ final int split = guid.indexOf(':');
+ final String name = guid.substring(0, split);
+ final Root root = mRoots.get(name);
+ if (root != null) {
+ final String path = Uri.decode(guid.substring(split + 1));
+ return new File(root.path, path);
+ }
+
+ throw new IllegalArgumentException("Failed to find root for " + guid);
}
- private void includeFileLocked(MatrixCursor cursor, int id) {
- final File file = mFiles.get(id);
+ private void includeFile(MatrixCursor cursor, String guid) {
int flags = 0;
+ final File file = guidToFile(guid);
+ if (file.isDirectory()) {
+ flags |= DocumentsContract.FLAG_SUPPORTS_SEARCH;
+ }
if (file.isDirectory() && file.canWrite()) {
flags |= DocumentsContract.FLAG_SUPPORTS_CREATE;
}
if (file.canWrite()) {
flags |= DocumentsContract.FLAG_SUPPORTS_RENAME;
+ flags |= DocumentsContract.FLAG_SUPPORTS_DELETE;
}
- final String mimeType = getTypeLocked(id);
+ final String mimeType = getTypeForFile(file);
if (mimeType.startsWith("image/")) {
flags |= DocumentsContract.FLAG_SUPPORTS_THUMBNAIL;
}
+ final long id = guid.hashCode();
cursor.addRow(new Object[] {
- id, file.getName(), file.length(), id, mimeType, file.lastModified(), flags });
+ id, file.getName(), file.length(), guid, mimeType, file.lastModified(), flags });
}
@Override
public String getType(Uri uri) {
switch (sMatcher.match(uri)) {
case URI_DOCS_ID: {
- final int id = Integer.parseInt(uri.getPathSegments().get(1));
- synchronized (mFiles) {
- return getTypeLocked(id);
- }
+ final String guid = uri.getPathSegments().get(1);
+ return getTypeForFile(guidToFile(guid));
}
default: {
throw new UnsupportedOperationException("Unsupported Uri " + uri);
@@ -151,16 +220,18 @@ public class ExternalStorageProvider extends ContentProvider {
}
}
- private String getTypeLocked(int id) {
- final File file = mFiles.get(id);
-
+ private String getTypeForFile(File file) {
if (file.isDirectory()) {
return DocumentsContract.MIME_TYPE_DIRECTORY;
+ } else {
+ return getTypeForName(file.getName());
}
+ }
- final int lastDot = file.getName().lastIndexOf('.');
+ private String getTypeForName(String name) {
+ final int lastDot = name.lastIndexOf('.');
if (lastDot >= 0) {
- final String extension = file.getName().substring(lastDot + 1);
+ final String extension = name.substring(lastDot + 1);
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) {
return mime;
@@ -174,12 +245,11 @@ public class ExternalStorageProvider extends ContentProvider {
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
switch (sMatcher.match(uri)) {
case URI_DOCS_ID: {
- final int id = Integer.parseInt(uri.getPathSegments().get(1));
- synchronized (mFiles) {
- final File file = mFiles.get(id);
- // TODO: turn into thumbnail
- return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(uri, mode));
- }
+ final String guid = uri.getPathSegments().get(1);
+ final File file = guidToFile(guid);
+
+ // TODO: offer as thumbnail
+ return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(uri, mode));
}
default: {
throw new UnsupportedOperationException("Unsupported Uri " + uri);
@@ -189,16 +259,84 @@ public class ExternalStorageProvider extends ContentProvider {
@Override
public Uri insert(Uri uri, ContentValues values) {
- throw new UnsupportedOperationException();
+ switch (sMatcher.match(uri)) {
+ case URI_DOCS_ID: {
+ final String guid = uri.getPathSegments().get(1);
+ final File parent = guidToFile(guid);
+
+ final String mimeType = values.getAsString(DocumentColumns.MIME_TYPE);
+ final String name = validateDisplayName(
+ values.getAsString(DocumentColumns.DISPLAY_NAME), mimeType);
+
+ final File file = new File(parent, name);
+ if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) {
+ if (!file.mkdir()) {
+ return null;
+ }
+
+ } else {
+ try {
+ if (!file.createNewFile()) {
+ return null;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to create file", e);
+ return null;
+ }
+ }
+
+ return DocumentsContract.buildDocumentUri(AUTHORITY, fileToGuid(file));
+ }
+ default: {
+ throw new UnsupportedOperationException("Unsupported Uri " + uri);
+ }
+ }
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException();
+ switch (sMatcher.match(uri)) {
+ case URI_DOCS_ID: {
+ final String guid = uri.getPathSegments().get(1);
+ final File file = guidToFile(guid);
+ final File newFile = new File(
+ file.getParentFile(), values.getAsString(DocumentColumns.DISPLAY_NAME));
+ return file.renameTo(newFile) ? 1 : 0;
+ }
+ default: {
+ throw new UnsupportedOperationException("Unsupported Uri " + uri);
+ }
+ }
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException();
+ switch (sMatcher.match(uri)) {
+ case URI_DOCS_ID: {
+ final String guid = uri.getPathSegments().get(1);
+ final File file = guidToFile(guid);
+ return file.delete() ? 1 : 0;
+ }
+ default: {
+ throw new UnsupportedOperationException("Unsupported Uri " + uri);
+ }
+ }
+ }
+
+ private String validateDisplayName(String displayName, String mimeType) {
+ if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) {
+ return displayName;
+ } else {
+ // Try appending meaningful extension if needed
+ if (!mimeType.equals(getTypeForName(displayName))) {
+ final String extension = MimeTypeMap.getSingleton()
+ .getExtensionFromMimeType(mimeType);
+ if (extension != null) {
+ displayName += "." + extension;
+ }
+ }
+
+ return displayName;
+ }
}
}
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity.xml b/packages/PrintSpooler/res/layout/print_job_config_activity.xml
index 32bc15aea164..a4105ea0ed45 100644
--- a/packages/PrintSpooler/res/layout/print_job_config_activity.xml
+++ b/packages/PrintSpooler/res/layout/print_job_config_activity.xml
@@ -34,12 +34,13 @@
android:layout_height="wrap_content"
android:layout_gravity="fill_horizontal"
android:layout_marginLeft="32dip"
+ android:layout_marginTop="32dip"
android:layout_marginRight="32dip"
android:layout_marginBottom="12dip"
android:layout_row="0"
android:layout_column="0"
android:layout_columnSpan="2"
- android:minHeight="?android:attr/listPreferredItemHeightSmall">
+ android:minHeight="?android:attr/listPreferredItemHeight">
</Spinner>
<!-- Copies -->
@@ -57,7 +58,8 @@
android:layout_gravity="bottom"
android:inputType="numberDecimal"
android:selectAllOnFocus="true"
- android:minWidth="150dip">
+ android:minWidth="150dip"
+ android:minHeight="?android:attr/listPreferredItemHeight">
</view>
<TextView
@@ -86,7 +88,8 @@
android:layout_marginBottom="12dip"
android:layout_row="2"
android:layout_column="1"
- android:minWidth="150dip">
+ android:minWidth="150dip"
+ android:minHeight="?android:attr/listPreferredItemHeight">
</Spinner>
<TextView
@@ -114,7 +117,8 @@
android:layout_marginBottom="12dip"
android:layout_row="4"
android:layout_column="0"
- android:minWidth="150dip">
+ android:minWidth="150dip"
+ android:minHeight="?android:attr/listPreferredItemHeight">
</Spinner>
<TextView
@@ -142,7 +146,8 @@
android:layout_marginBottom="12dip"
android:layout_row="4"
android:layout_column="1"
- android:minWidth="150dip">
+ android:minWidth="150dip"
+ android:minHeight="?android:attr/listPreferredItemHeight">
</Spinner>
<TextView
@@ -169,7 +174,8 @@
android:layout_marginRight="12dip"
android:layout_row="6"
android:layout_column="0"
- android:minWidth="150dip">
+ android:minWidth="150dip"
+ android:minHeight="?android:attr/listPreferredItemHeight">
</Spinner>
<view
@@ -186,10 +192,12 @@
android:minWidth="150dip"
android:hint="@string/pages_range_example"
android:inputType="textNoSuggestions"
- android:visibility="gone">
+ android:visibility="gone"
+ android:minHeight="?android:attr/listPreferredItemHeight">
</view>
<TextView
+ android:id="@+id/page_range_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="32dip"
@@ -231,7 +239,8 @@
android:layout_columnSpan="2"
android:text="@string/print_preview"
android:gravity="left|center_vertical"
- android:background="?android:attr/selectableItemBackground">
+ android:background="?android:attr/selectableItemBackground"
+ android:minHeight="?android:attr/listPreferredItemHeight">
</Button>
<ImageView
@@ -269,7 +278,8 @@
android:layout_columnSpan="2"
android:padding="0dip"
android:text="@string/print_button"
- android:background="?android:attr/selectableItemBackground">
+ android:background="?android:attr/selectableItemBackground"
+ android:minHeight="?android:attr/listPreferredItemHeight">
</Button>
</GridLayout>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 27540d7d6838..176269364ef8 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<resources>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Title of the PrintSpooler application. [CHAR LIMIT=50] -->
<string name="app_label">Print Spooler</string>
@@ -38,7 +38,7 @@
<string name="label_orientation">ORIENTATION</string>
<!-- Label of the page selection widget. [CHAR LIMIT=20] -->
- <string name="label_pages">PAGES</string>
+ <string name="label_pages">PAGES (<xliff:g id="page_count" example="5">%1$s</xliff:g>)</string>
<!-- Page range exmple used as a hint of how to specify such. [CHAR LIMIT=15] -->
<string name="pages_range_example">e.g. 1&#8211;5, 8</string>
@@ -52,6 +52,9 @@
<!-- Title of the message that the printing application crashed. [CHAR LIMIT=50] -->
<string name="printing_app_crashed">Printing app crashed</string>
+ <!-- Title if the number of pages in a printed document is unknown. [CHAR LIMIT=20] -->
+ <string name="page_count_unknown">unknown</string>
+
<!-- Color mode labels. -->
<string-array name="color_mode_labels">
<!-- Color modelabel: Monochrome color scheme, e.g. one color is used. [CHAR LIMIT=20] -->
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
index 1e1cc24f263e..d61fd2cadc5a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
@@ -24,9 +24,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -34,24 +32,27 @@ import android.os.IBinder.DeathRecipient;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
-import android.os.UserHandle;
+import android.print.ILayoutResultCallback;
import android.print.IPrintDocumentAdapter;
import android.print.IPrinterDiscoveryObserver;
+import android.print.IWriteResultCallback;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintAttributes.MediaSize;
-import android.print.PrintDocumentAdapter.LayoutResultCallback;
-import android.print.PrintDocumentAdapter.WriteResultCallback;
+import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
import android.print.PrintJobInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.text.Editable;
import android.text.TextUtils;
+import android.text.TextUtils.SimpleStringSplitter;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Choreographer;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
@@ -64,10 +65,13 @@ import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
+import android.widget.Toast;
-import java.io.File;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -76,15 +80,31 @@ import java.util.regex.Pattern;
*/
public class PrintJobConfigActivity extends Activity {
- private static final boolean DEBUG = false;
+ private static final String LOG_TAG = "PrintJobConfigActivity";
- private static final String LOG_TAG = PrintJobConfigActivity.class.getSimpleName();
+ private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
- public static final String EXTRA_PRINTABLE = "printable";
- public static final String EXTRA_APP_ID = "appId";
- public static final String EXTRA_ATTRIBUTES = "attributes";
+ private static final boolean LIVE_PREVIEW_SUPPORTED = false;
+
+ public static final String EXTRA_PRINT_DOCUMENT_ADAPTER = "printDocumentAdapter";
+ public static final String EXTRA_PRINT_ATTRIBUTES = "printAttributes";
public static final String EXTRA_PRINT_JOB_ID = "printJobId";
+ private static final int CONTROLLER_STATE_INITIALIZED = 1;
+ private static final int CONTROLLER_STATE_STARTED = 2;
+ private static final int CONTROLLER_STATE_LAYOUT_STARTED = 3;
+ private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 4;
+ private static final int CONTROLLER_STATE_WRITE_STARTED = 5;
+ private static final int CONTROLLER_STATE_WRITE_COMPLETED = 6;
+ private static final int CONTROLLER_STATE_FINISHED = 7;
+ private static final int CONTROLLER_STATE_FAILED = 8;
+ private static final int CONTROLLER_STATE_CANCELLED = 9;
+
+ private static final int EDITOR_STATE_INITIALIZED = 1;
+ private static final int EDITOR_STATE_CONFIRMED_PRINT = 2;
+ private static final int EDITOR_STATE_CONFIRMED_PREVIEW = 3;
+ private static final int EDITOR_STATE_CANCELLED = 4;
+
private static final int MIN_COPIES = 1;
private static final Pattern PATTERN_DIGITS = Pattern.compile("\\d");
@@ -95,31 +115,12 @@ public class PrintJobConfigActivity extends Activity {
private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile(
"([0-9]+[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*[,]?[\\s]*)+");
- private final PrintSpooler mPrintSpooler = PrintSpooler.getInstance(this);
-
- private Handler mHandler;
-
- private Editor mEditor;
-
- private IPrinterDiscoveryObserver mPrinterDiscoveryObserver;
-
- private int mAppId;
- private int mPrintJobId;
+ public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {PageRange.ALL_PAGES};
private final PrintAttributes mOldPrintAttributes = new PrintAttributes.Builder().create();
private final PrintAttributes mCurrPrintAttributes = new PrintAttributes.Builder().create();
private final PrintAttributes mTempPrintAttributes = new PrintAttributes.Builder().create();
- private RemotePrintDocumentAdapter mRemotePrintAdapter;
-
- private boolean mPrintConfirmed;
-
- private boolean mStarted;
-
- private IBinder mIPrintDocumentAdapter;
-
- private PrintDocumentInfo mPrintDocumentInfo;
-
private final DeathRecipient mDeathRecipient = new DeathRecipient() {
@Override
public void binderDied() {
@@ -127,369 +128,492 @@ public class PrintJobConfigActivity extends Activity {
}
};
- @Override
- protected void onDestroy() {
- mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
- super.onDestroy();
- }
+ private PrintSpooler mSpooler;
+ private Editor mEditor;
+ private Document mDocument;
+ private PrintController mController;
+ private PrinterDiscoveryObserver mPrinterDiscoveryObserver;
+
+ private int mPrintJobId;
+
+ private IBinder mIPrintDocumentAdapter;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.print_job_config_activity);
- getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
- | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+ getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+
+ Bundle extras = getIntent().getExtras();
+
+ mPrintJobId = extras.getInt(EXTRA_PRINT_JOB_ID, -1);
+ if (mPrintJobId < 0) {
+ throw new IllegalArgumentException("Invalid print job id: " + mPrintJobId);
+ }
+
+ mIPrintDocumentAdapter = extras.getBinder(EXTRA_PRINT_DOCUMENT_ADAPTER);
+ if (mIPrintDocumentAdapter == null) {
+ throw new IllegalArgumentException("PrintDocumentAdapter cannot be null");
+ }
+
+ PrintAttributes attributes = getIntent().getParcelableExtra(EXTRA_PRINT_ATTRIBUTES);
+ if (attributes != null) {
+ mCurrPrintAttributes.copyFrom(attributes);
+ }
- mHandler = new MyHandler(Looper.getMainLooper());
+ mSpooler = PrintSpooler.getInstance(this);
mEditor = new Editor();
+ mDocument = new Document();
+ mController = new PrintController(new RemotePrintDocumentAdapter(
+ IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
+ mSpooler.generateFileForPrintJob(mPrintJobId)));
}
@Override
protected void onResume() {
super.onResume();
- mPrintSpooler.startPrinterDiscovery(mPrinterDiscoveryObserver);
- notifyPrintableStartIfNeeded();
+ try {
+ mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException re) {
+ finish();
+ return;
+ }
+ mController.initialize();
+ mEditor.initialize();
+ mPrinterDiscoveryObserver = new PrinterDiscoveryObserver(mEditor, getMainLooper());
+ mSpooler.startPrinterDiscovery(mPrinterDiscoveryObserver);
}
@Override
protected void onPause() {
+ mSpooler.stopPrinterDiscovery();
+ mPrinterDiscoveryObserver.destroy();
+ mPrinterDiscoveryObserver = null;
+ if (mController.isCancelled() || mController.isFailed()) {
+ mSpooler.setPrintJobState(mPrintJobId,
+ PrintJobInfo.STATE_CANCELED);
+ } else if (mController.hasStarted()) {
+ mController.finish();
+ if (mEditor.isPrintConfirmed()) {
+ if (mController.isFinished()) {
+ mSpooler.setPrintJobState(mPrintJobId,
+ PrintJobInfo.STATE_QUEUED);
+ } else {
+ mSpooler.setPrintJobState(mPrintJobId,
+ PrintJobInfo.STATE_CANCELED);
+ }
+ }
+ }
+ mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
super.onPause();
- mPrintSpooler.stopPrinterDiscovery();
- notifyPrintableFinishIfNeeded();
}
- private void notifyPrintableStartIfNeeded() {
- if (mEditor.getCurrentPrinter() == null
- || mStarted) {
- return;
+ public boolean onTouchEvent(MotionEvent event) {
+ if (!mEditor.isPrintConfirmed() && !mEditor.isPreviewConfirmed()
+ && getWindow().shouldCloseOnTouch(this, event)) {
+ if (!mController.isWorking()) {
+ PrintJobConfigActivity.this.finish();
+ }
+ mEditor.cancel();
+ return true;
}
- mStarted = true;
- mRemotePrintAdapter.start();
+ return super.onTouchEvent(event);
}
- private void updatePrintableContentIfNeeded() {
- if (!mStarted) {
- return;
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ event.startTracking();
}
+ return super.onKeyDown(keyCode, event);
+ }
- mPrintSpooler.setPrintJobAttributes(mPrintJobId, mCurrPrintAttributes);
-
- mRemotePrintAdapter.cancel();
- mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT_FINISHED);
- mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT_FAILED);
-
- // TODO: Implement setting the print preview attribute
- mRemotePrintAdapter.layout(mOldPrintAttributes,
- mCurrPrintAttributes, new LayoutResultCallback() {
- @Override
- public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
- mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FINISHED, changed ? 1 : 0,
- 0, info).sendToTarget();
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
+ && !event.isCanceled()) {
+ if (!mController.isWorking()) {
+ PrintJobConfigActivity.this.finish();
}
+ mEditor.cancel();
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
- @Override
- public void onLayoutFailed(CharSequence error) {
- mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FAILED, error).sendToTarget();
- }
- }, new Bundle());
+ private boolean printAttributesChanged() {
+ return !mOldPrintAttributes.equals(mCurrPrintAttributes);
}
- private void handleOnLayoutFinished(PrintDocumentInfo info, boolean changed) {
- mPrintDocumentInfo = info;
+ private class PrintController {
+ private final AtomicInteger mRequestCounter = new AtomicInteger();
+
+ private final RemotePrintDocumentAdapter mRemotePrintAdapter;
+
+ private final Handler mHandler;
- mEditor.updateUiIfNeeded();
+ private int mControllerState = CONTROLLER_STATE_INITIALIZED;
- // TODO: Handle the case of unchanged content
- mPrintSpooler.setPrintJobPrintDocumentInfo(mPrintJobId, info);
+ private PageRange[] mRequestedPages;
- // TODO: Implement page selector.
- final List<PageRange> pages = new ArrayList<PageRange>();
- pages.add(PageRange.ALL_PAGES);
+ private Bundle mMetadata = new Bundle();
- mRemotePrintAdapter.write(pages, new WriteResultCallback() {
+ private final ILayoutResultCallback mILayoutResultCallback =
+ new ILayoutResultCallback.Stub() {
@Override
- public void onWriteFinished(List<PageRange> pages) {
- mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FINISHED, pages).sendToTarget();
+ public void onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence) {
+ if (mRequestCounter.get() == sequence) {
+ mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FINISHED, changed ? 1 : 0,
+ 0, info).sendToTarget();
+ }
}
@Override
- public void onWriteFailed(CharSequence error) {
- mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FAILED, error).sendToTarget();
+ public void onLayoutFailed(CharSequence error, int sequence) {
+ if (mRequestCounter.get() == sequence) {
+ mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT_FAILED, error).sendToTarget();
+ }
}
- });
- }
-
- private void handleOnLayoutFailed(CharSequence error) {
- Log.e(LOG_TAG, "Error during layout: " + error);
- finishActivity(Activity.RESULT_CANCELED);
- }
+ };
- private void handleOnWriteFinished(List<PageRange> pages) {
- // TODO: Now we have to allow the preview button
- mEditor.updatePrintPreview(mRemotePrintAdapter.getFile());
- }
+ private IWriteResultCallback mIWriteResultCallback = new IWriteResultCallback.Stub() {
+ @Override
+ public void onWriteFinished(PageRange[] pages, int sequence) {
+ if (mRequestCounter.get() == sequence) {
+ mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FINISHED, pages).sendToTarget();
+ }
+ }
- private void handleOnWriteFailed(CharSequence error) {
- Log.e(LOG_TAG, "Error write layout: " + error);
- finishActivity(Activity.RESULT_CANCELED);
- }
+ @Override
+ public void onWriteFailed(CharSequence error, int sequence) {
+ if (mRequestCounter.get() == sequence) {
+ mHandler.obtainMessage(MyHandler.MSG_ON_WRITE_FAILED, error).sendToTarget();
+ }
+ }
+ };
- private void notifyPrintableFinishIfNeeded() {
- if (!mStarted) {
- return;
+ public PrintController(RemotePrintDocumentAdapter adapter) {
+ mRemotePrintAdapter = adapter;
+ mHandler = new MyHandler(Looper.getMainLooper());
}
- if (!mPrintConfirmed) {
- mRemotePrintAdapter.cancel();
+ public void initialize() {
+ mControllerState = CONTROLLER_STATE_INITIALIZED;
}
- mRemotePrintAdapter.finish();
- PrinterInfo printer = mEditor.getCurrentPrinter();
- // If canceled or no printer, nothing to do.
- if (!mPrintConfirmed || printer == null) {
- // Update the print job's status.
- mPrintSpooler.setPrintJobState(mPrintJobId,
- PrintJobInfo.STATE_CANCELED);
- return;
+ public void cancel() {
+ mControllerState = CONTROLLER_STATE_CANCELLED;
}
- // Update the print job's printer.
- mPrintSpooler.setPrintJobPrinterId(mPrintJobId, printer.getId());
+ public boolean isCancelled() {
+ return (mControllerState == CONTROLLER_STATE_CANCELLED);
+ }
- // Update the print job's status.
- mPrintSpooler.setPrintJobState(mPrintJobId,
- PrintJobInfo.STATE_QUEUED);
+ public boolean isFinished() {
+ return (mControllerState == CONTROLLER_STATE_FINISHED);
+ }
- if (DEBUG) {
- if (mPrintConfirmed) {
- File file = mRemotePrintAdapter.getFile();
- if (file.exists()) {
- new ViewSpooledFileAsyncTask(file).executeOnExecutor(
- AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
- }
- }
+ public boolean isFailed() {
+ return (mControllerState == CONTROLLER_STATE_FAILED);
}
- }
- private boolean hasPdfViewer() {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setType("application/pdf");
- return !getPackageManager().queryIntentActivities(intent,
- PackageManager.MATCH_DEFAULT_ONLY).isEmpty();
- }
+ public boolean hasStarted() {
+ return mControllerState >= CONTROLLER_STATE_STARTED;
+ }
- // Caution: Use this only for debugging
- private final class ViewSpooledFileAsyncTask extends AsyncTask<Void, Void, Void> {
+ public boolean hasPerformedLayout() {
+ return mControllerState >= CONTROLLER_STATE_LAYOUT_COMPLETED;
+ }
- private final File mFile;
+ public boolean isWorking() {
+ return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED
+ || mControllerState == CONTROLLER_STATE_WRITE_STARTED;
+ }
- public ViewSpooledFileAsyncTask(File file) {
- mFile = file;
+ public void start() {
+ mControllerState = CONTROLLER_STATE_STARTED;
+ mRemotePrintAdapter.start();
}
- @Override
- protected Void doInBackground(Void... params) {
- mFile.setExecutable(true, false);
- mFile.setWritable(true, false);
- mFile.setReadable(true, false);
+ public void update() {
+ if (!printAttributesChanged()) {
+ // If the attributes changes, then we do not do a layout but may
+ // have to ask the app to write some pages. Hence, pretend layout
+ // completed and nothing changed, so we handle writing as usual.
+ handleOnLayoutFinished(mDocument.info, false);
+ } else {
+ mSpooler.setPrintJobAttributesNoPersistence(mPrintJobId, mCurrPrintAttributes);
- final long identity = Binder.clearCallingIdentity();
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(Uri.fromFile(mFile), "application/pdf");
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivityAsUser(intent, null, UserHandle.CURRENT);
- Binder.restoreCallingIdentity(identity);
- return null;
- }
- }
+ mMetadata.putBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW,
+ !mEditor.isPrintConfirmed());
- private final class PrintDiscoveryObserver extends IPrinterDiscoveryObserver.Stub {
- private static final int MESSAGE_ADD_DICOVERED_PRINTERS = 1;
- private static final int MESSAGE_REMOVE_DICOVERED_PRINTERS = 2;
+ mControllerState = CONTROLLER_STATE_LAYOUT_STARTED;
- private final Handler mHandler;
+ mRemotePrintAdapter.layout(mOldPrintAttributes, mCurrPrintAttributes,
+ mILayoutResultCallback, mMetadata, mRequestCounter.incrementAndGet());
- @SuppressWarnings("unchecked")
- public PrintDiscoveryObserver(Looper looper) {
- mHandler = new Handler(looper, null, true) {
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MESSAGE_ADD_DICOVERED_PRINTERS: {
- List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
- mEditor.addPrinters(printers);
- } break;
- case MESSAGE_REMOVE_DICOVERED_PRINTERS: {
- List<PrinterId> printerIds = (List<PrinterId>) message.obj;
- mEditor.removePrinters(printerIds);
- } break;
- }
- }
- };
+ mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
+ }
}
- @Override
- public void addDiscoveredPrinters(List<PrinterInfo> printers) {
- mHandler.obtainMessage(MESSAGE_ADD_DICOVERED_PRINTERS, printers).sendToTarget();
+ public void finish() {
+ mControllerState = CONTROLLER_STATE_FINISHED;
+ mRemotePrintAdapter.finish();
}
- @Override
- public void removeDiscoveredPrinters(List<PrinterId> printers) {
- mHandler.obtainMessage(MESSAGE_REMOVE_DICOVERED_PRINTERS, printers).sendToTarget();
- }
- }
+ private void handleOnLayoutFinished(PrintDocumentInfo info, boolean layoutChanged) {
+ if (isCancelled()) {
+ if (mEditor.isDone()) {
+ PrintJobConfigActivity.this.finish();
+ }
+ return;
+ }
- private final class SpinnerItem<T> {
- final T value;
- CharSequence label;
+ mControllerState = CONTROLLER_STATE_LAYOUT_COMPLETED;
- public SpinnerItem(T value, CharSequence label) {
- this.value = value;
- this.label = label;
- }
+ // If the info changed, we update the document and the print job,
+ // and update the UI since the the page range selection may have
+ // become invalid.
+ final boolean infoChanged = !info.equals(mDocument.info);
+ if (infoChanged) {
+ mDocument.info = info;
+ mSpooler.setPrintJobPrintDocumentInfoNoPersistence(mPrintJobId, info);
+ mEditor.updateUi();
+ }
- public String toString() {
- return label.toString();
- }
- }
+ // If the document info or the layout changed, then
+ // drop the pages since we have to fetch them again.
+ if (infoChanged || layoutChanged) {
+ mDocument.pages = null;
+ }
- /**
- * An instance of this class class is intended to be the first focusable
- * in a layout to which the system automatically gives focus. It performs
- * some voodoo to avoid the first tap on it to start an edit mode, rather
- * to bring up the IME, i.e. to get the behavior as if the view was not
- * focused.
- */
- public static final class CustomEditText extends EditText {
- private boolean mClickedBeforeFocus;
+ // No pages means that the user selected an invalid range while we
+ // were doing a layout or the layout returned a document info for
+ // which the selected range is invalid. In such a case we do not
+ // write anything and wait for the user to fix the range which will
+ // trigger an update.
+ mRequestedPages = mEditor.getRequestedPages();
+ if (mRequestedPages == null) {
+ if (mEditor.isDone()) {
+ PrintJobConfigActivity.this.finish();
+ }
+ return;
+ }
- public CustomEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
+ // If the info and the layout did not change and we already have
+ // the requested pages, then nothing else to do.
+ if (!infoChanged && !layoutChanged
+ && PageRangeUtils.contains(mDocument.pages, mRequestedPages)) {
+ if (mEditor.isDone()) {
+ PrintJobConfigActivity.this.finish();
+ }
+ return;
+ }
- @Override
- public boolean performClick() {
- super.performClick();
- if (isFocused() && !mClickedBeforeFocus) {
- clearFocus();
- requestFocus();
+ // If we do not support live preview and the current layout is
+ // not for preview purposes, i.e. the user did not poke the
+ // preview button, then just skip the write.
+ if (!LIVE_PREVIEW_SUPPORTED && !mEditor.isPreviewConfirmed()
+ && mMetadata.getBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW)) {
+ if (mEditor.isDone()) {
+ PrintJobConfigActivity.this.finish();
+ }
+ return;
}
- mClickedBeforeFocus = true;
- return true;
+
+ // Request a write of the pages of interest.
+ mControllerState = CONTROLLER_STATE_WRITE_STARTED;
+ mRemotePrintAdapter.write(mRequestedPages, mIWriteResultCallback,
+ mRequestCounter.incrementAndGet());
}
- @Override
- public void setError(CharSequence error, Drawable icon) {
- setCompoundDrawables(null, null, icon, null);
+ private void handleOnLayoutFailed(CharSequence error) {
+ mControllerState = CONTROLLER_STATE_FAILED;
+ // TODO: We need some UI for announcing an error.
+ Log.e(LOG_TAG, "Error during layout: " + error);
+ PrintJobConfigActivity.this.finish();
}
- protected void onFocusChanged(boolean gainFocus, int direction,
- Rect previouslyFocusedRect) {
- if (!gainFocus) {
- mClickedBeforeFocus = false;
+ private void handleOnWriteFinished(PageRange[] pages) {
+ if (isCancelled()) {
+ if (mEditor.isDone()) {
+ PrintJobConfigActivity.this.finish();
+ }
+ return;
}
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
- }
- }
- private final class MyHandler extends Handler {
- public static final int MSG_ON_LAYOUT_FINISHED = 1;
- public static final int MSG_ON_LAYOUT_FAILED = 2;
- public static final int MSG_ON_WRITE_FINISHED = 3;
- public static final int MSG_ON_WRITE_FAILED = 4;
+ mControllerState = CONTROLLER_STATE_WRITE_COMPLETED;
+
+ // Update which pages we have fetched.
+ mDocument.pages = PageRangeUtils.normalize(pages);
- public MyHandler(Looper looper) {
- super(looper, null, false);
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Requested: " + Arrays.toString(mRequestedPages)
+ + " and got: " + Arrays.toString(mDocument.pages));
+ }
+
+ // 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(mDocument.pages, mRequestedPages)) {
+ // We got a document with exactly the pages we wanted. Hence,
+ // the printer has to print all pages in the data.
+ mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, ALL_PAGES_ARRAY);
+ } else if (Arrays.equals(mDocument.pages, ALL_PAGES_ARRAY)) {
+ // We requested specific pages but got all of them. Hence,
+ // the printer has to print only the requested pages.
+ mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, mRequestedPages);
+ } else if (PageRangeUtils.contains(mDocument.pages, mRequestedPages)) {
+ // We requested specific pages and got more but not all pages.
+ // Hence, we have to offset appropriately the printed pages to
+ // excle the pages we did not request. Note that pages is
+ // guaranteed to be not null and not empty.
+ final int offset = mDocument.pages[0].getStart() - pages[0].getStart();
+ PageRange[] offsetPages = Arrays.copyOf(mDocument.pages, mDocument.pages.length);
+ PageRangeUtils.offsetStart(offsetPages, offset);
+ mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, offsetPages);
+ } else if (Arrays.equals(mRequestedPages, ALL_PAGES_ARRAY)
+ && mDocument.pages.length == 1 && mDocument.pages[0].getStart() == 0
+ && mDocument.pages[0].getEnd() == mDocument.info.getPageCount() - 1) {
+ // 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.
+ mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, mDocument.pages);
+ } else {
+ // We did not get the pages we requested, then the application
+ // misbehaves, so we fail quickly.
+ // TODO: We need some UI for announcing an error.
+ mControllerState = CONTROLLER_STATE_FAILED;
+ Log.e(LOG_TAG, "Received invalid pages from the app");
+ PrintJobConfigActivity.this.finish();
+ }
+
+ if (mEditor.isDone()) {
+ PrintJobConfigActivity.this.finish();
+ }
}
- @Override
- @SuppressWarnings("unchecked")
- public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_ON_LAYOUT_FINISHED: {
- PrintDocumentInfo info = (PrintDocumentInfo) message.obj;
- final boolean changed = (message.arg1 == 1);
- handleOnLayoutFinished(info, changed);
- } break;
-
- case MSG_ON_LAYOUT_FAILED: {
- CharSequence error = (CharSequence) message.obj;
- handleOnLayoutFailed(error);
- } break;
-
- case MSG_ON_WRITE_FINISHED: {
- List<PageRange> pages = (List<PageRange>) message.obj;
- handleOnWriteFinished(pages);
- } break;
-
- case MSG_ON_WRITE_FAILED: {
- CharSequence error = (CharSequence) message.obj;
- handleOnWriteFailed(error);
- } break;
+ private void handleOnWriteFailed(CharSequence error) {
+ mControllerState = CONTROLLER_STATE_FAILED;
+ Log.e(LOG_TAG, "Error during write: " + error);
+ PrintJobConfigActivity.this.finish();
+ }
+
+ private final class MyHandler extends Handler {
+ public static final int MSG_ON_LAYOUT_FINISHED = 1;
+ public static final int MSG_ON_LAYOUT_FAILED = 2;
+ public static final int MSG_ON_WRITE_FINISHED = 3;
+ public static final int MSG_ON_WRITE_FAILED = 4;
+
+ public MyHandler(Looper looper) {
+ super(looper, null, false);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_ON_LAYOUT_FINISHED: {
+ PrintDocumentInfo info = (PrintDocumentInfo) message.obj;
+ final boolean changed = (message.arg1 == 1);
+ mController.handleOnLayoutFinished(info, changed);
+ } break;
+
+ case MSG_ON_LAYOUT_FAILED: {
+ CharSequence error = (CharSequence) message.obj;
+ mController.handleOnLayoutFailed(error);
+ } break;
+
+ case MSG_ON_WRITE_FINISHED: {
+ PageRange[] pages = (PageRange[]) message.obj;
+ mController.handleOnWriteFinished(pages);
+ } break;
+
+ case MSG_ON_WRITE_FAILED: {
+ CharSequence error = (CharSequence) message.obj;
+ mController.handleOnWriteFailed(error);
+ } break;
+ }
}
}
}
- private class Editor {
- private EditText mCopiesEditText;
+ private final class Editor {
+ private final EditText mCopiesEditText;
+
+ private final TextView mRangeTitle;
+ private final EditText mRangeEditText;
- private EditText mRangeEditText;
+ private final Spinner mDestinationSpinner;
+ private final ArrayAdapter<SpinnerItem<PrinterInfo>> mDestinationSpinnerAdapter;
- private Spinner mDestinationSpinner;
- public ArrayAdapter<SpinnerItem<PrinterInfo>> mDestinationSpinnerAdapter;
+ private final Spinner mMediaSizeSpinner;
+ private final ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
- private Spinner mMediaSizeSpinner;
- public ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
+ private final Spinner mColorModeSpinner;
+ private final ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
- private Spinner mColorModeSpinner;
- public ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
+ private final Spinner mOrientationSpinner;
+ private final ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
- private Spinner mOrientationSpinner;
- public ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
+ private final Spinner mRangeOptionsSpinner;
+ private final ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter;
- private Spinner mRangeOptionsSpinner;
- public ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter;
+ private final SimpleStringSplitter mStringCommaSplitter =
+ new SimpleStringSplitter(',');
- private Button mPrintPreviewButton;
+ private final Button mPrintPreviewButton;
- private Button mPrintButton;
+ private final Button mPrintButton;
private final OnItemSelectedListener mOnItemSelectedListener =
new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
if (spinner == mDestinationSpinner) {
- mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
mCurrPrintAttributes.clear();
- final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
- if (selectedIndex >= 0) {
- mDestinationSpinnerAdapter.getItem(selectedIndex).value.getDefaults(
- mCurrPrintAttributes);
+ SpinnerItem<PrinterInfo> dstItem = mDestinationSpinnerAdapter.getItem(position);
+ if (dstItem != null) {
+ PrinterInfo printer = dstItem.value;
+ mSpooler.setPrintJobPrinterIdNoPersistence(mPrintJobId, printer.getId());
+ printer.getDefaults(mCurrPrintAttributes);
+ if (!printer.hasAllRequiredAttributes()) {
+ List<PrinterId> printerIds = new ArrayList<PrinterId>();
+ printerIds.add(printer.getId());
+ mSpooler.onReqeustUpdatePrinters(printerIds);
+ //TODO: We need a timeout for the update.
+ } else {
+ if (!mController.hasStarted()) {
+ mController.start();
+ }
+ if (!hasErrors()) {
+ mController.update();
+ }
+ }
}
- updateUiIfNeeded();
- notifyPrintableStartIfNeeded();
- updatePrintableContentIfNeeded();
+ updateUi();
} else if (spinner == mMediaSizeSpinner) {
SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
- mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
mCurrPrintAttributes.setMediaSize(mediaItem.value);
- updatePrintableContentIfNeeded();
+ if (!hasErrors()) {
+ mController.update();
+ }
} else if (spinner == mColorModeSpinner) {
SpinnerItem<Integer> colorModeItem =
mColorModeSpinnerAdapter.getItem(position);
- mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
mCurrPrintAttributes.setColorMode(colorModeItem.value);
- updatePrintableContentIfNeeded();
+ if (!hasErrors()) {
+ mController.update();
+ }
} else if (spinner == mOrientationSpinner) {
SpinnerItem<Integer> orientationItem =
mOrientationSpinnerAdapter.getItem(position);
- mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
mCurrPrintAttributes.setOrientation(orientationItem.value);
- updatePrintableContentIfNeeded();
+ if (!hasErrors()) {
+ mController.update();
+ }
} else if (spinner == mRangeOptionsSpinner) {
- updateUiIfNeeded();
- updatePrintableContentIfNeeded();
+ updateUi();
+ if (!hasErrors()) {
+ mController.update();
+ }
}
}
@@ -512,19 +636,28 @@ public class PrintJobConfigActivity extends Activity {
@Override
public void afterTextChanged(Editable editable) {
+ final boolean hadErrors = hasErrors();
+
if (editable.length() == 0) {
mCopiesEditText.setError("");
- mPrintButton.setEnabled(false);
+ updateUi();
return;
}
+
final int copies = Integer.parseInt(editable.toString());
if (copies < MIN_COPIES) {
mCopiesEditText.setError("");
- mPrintButton.setEnabled(false);
+ updateUi();
return;
}
- mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
- mCurrPrintAttributes.setCopies(copies);
+
+ mCopiesEditText.setError(null);
+ mSpooler.setPrintJobCopiesNoPersistence(mPrintJobId, copies);
+ updateUi();
+
+ if (hadErrors && !hasErrors() && printAttributesChanged()) {
+ mController.update();
+ }
}
};
@@ -541,18 +674,20 @@ public class PrintJobConfigActivity extends Activity {
@Override
public void afterTextChanged(Editable editable) {
+ final boolean hadErrors = hasErrors();
+
String text = editable.toString();
if (TextUtils.isEmpty(text)) {
mRangeEditText.setError("");
- mPrintButton.setEnabled(false);
+ updateUi();
return;
}
String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////");
if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) {
mRangeEditText.setError("");
- mPrintButton.setEnabled(false);
+ updateUi();
return;
}
@@ -560,100 +695,36 @@ public class PrintJobConfigActivity extends Activity {
while (matcher.find()) {
String numericString = text.substring(matcher.start(), matcher.end());
final int pageIndex = Integer.parseInt(numericString);
- if (pageIndex < 1 || pageIndex > mPrintDocumentInfo.getPageCount()) {
+ if (pageIndex < 1 || pageIndex > mDocument.info.getPageCount()) {
mRangeEditText.setError("");
- mPrintButton.setEnabled(false);
+ updateUi();
return;
}
}
+ //TODO: Catch the error if start is less grater than the end.
+
mRangeEditText.setError(null);
mPrintButton.setEnabled(true);
- }
- };
-
- public Editor() {
- Bundle extras = getIntent().getExtras();
-
- mPrintJobId = extras.getInt(EXTRA_PRINT_JOB_ID, -1);
- if (mPrintJobId < 0) {
- throw new IllegalArgumentException("Invalid print job id: " + mPrintJobId);
- }
-
- mAppId = extras.getInt(EXTRA_APP_ID, -1);
- if (mAppId < 0) {
- throw new IllegalArgumentException("Invalid app id: " + mAppId);
- }
+ updateUi();
- PrintAttributes attributes = getIntent().getParcelableExtra(EXTRA_ATTRIBUTES);
- if (attributes == null) {
- mCurrPrintAttributes.copyFrom(attributes);
- }
-
- mIPrintDocumentAdapter = extras.getBinder(EXTRA_PRINTABLE);
- if (mIPrintDocumentAdapter == null) {
- throw new IllegalArgumentException("Printable cannot be null");
- }
- mRemotePrintAdapter = new RemotePrintDocumentAdapter(
- IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
- mPrintSpooler.generateFileForPrintJob(mPrintJobId));
-
- try {
- mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
- } catch (RemoteException re) {
- finish();
+ if (hadErrors && !hasErrors() && printAttributesChanged()) {
+ updateUi();
+ }
}
+ };
- mPrinterDiscoveryObserver = new PrintDiscoveryObserver(getMainLooper());
-
- bindUi();
- }
+ private int mEditorState;
- private void bindUi() {
+ public Editor() {
// Copies
mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
- mCopiesEditText.setText(String.valueOf(MIN_COPIES));
mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
- mCopiesEditText.setText(String.valueOf(
- Math.max(mCurrPrintAttributes.getCopies(), MIN_COPIES)));
mCopiesEditText.selectAll();
// Destination.
mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
- mDestinationSpinnerAdapter = new ArrayAdapter<SpinnerItem<PrinterInfo>>(
- PrintJobConfigActivity.this, R.layout.spinner_dropdown_item) {
- @Override
- public View getDropDownView(int position, View convertView,
- ViewGroup parent) {
- return getView(position, convertView, parent);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = getLayoutInflater().inflate(
- R.layout.spinner_dropdown_item, parent, false);
- }
-
- PrinterInfo printerInfo = getItem(position).value;
- TextView title = (TextView) convertView.findViewById(R.id.title);
- title.setText(printerInfo.getLabel());
-
- try {
- TextView subtitle = (TextView)
- convertView.findViewById(R.id.subtitle);
- PackageManager pm = getPackageManager();
- PackageInfo packageInfo = pm.getPackageInfo(
- printerInfo.getId().getService().getPackageName(), 0);
- subtitle.setText(packageInfo.applicationInfo.loadLabel(pm));
- subtitle.setVisibility(View.VISIBLE);
- } catch (NameNotFoundException nnfe) {
- /* ignore */
- }
-
- return convertView;
- }
- };
+ mDestinationSpinnerAdapter = new DestinationAdapter();
mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
@@ -682,6 +753,7 @@ public class PrintJobConfigActivity extends Activity {
mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// Range
+ mRangeTitle = (TextView) findViewById(R.id.page_range_title);
mRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
mRangeEditText.addTextChangedListener(mRangeTextWatcher);
@@ -690,8 +762,6 @@ public class PrintJobConfigActivity extends Activity {
mRangeOptionsSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
PrintJobConfigActivity.this,
R.layout.spinner_dropdown_item, R.id.title);
- mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter);
- mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
final int[] rangeOptionsValues = getResources().getIntArray(
R.array.page_options_values);
String[] rangeOptionsLabels = getResources().getStringArray(
@@ -701,13 +771,29 @@ public class PrintJobConfigActivity extends Activity {
mRangeOptionsSpinnerAdapter.add(new SpinnerItem<Integer>(
rangeOptionsValues[i], rangeOptionsLabels[i]));
}
+ mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter);
mRangeOptionsSpinner.setSelection(0);
+ // Here is some voodoo to circumvent the weird behavior of AdapterView
+ // in which a selection listener may get a callback for an event that
+ // happened before the listener was registered. The reason for that is
+ // that the selection change is handled on the next layout pass.
+ Choreographer.getInstance().postCallbackDelayed(Choreographer.CALLBACK_TRAVERSAL,
+ new Runnable() {
+ @Override
+ public void run() {
+ mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+ }
+ }, null, Choreographer.getFrameDelay() * 2);
mPrintPreviewButton = (Button) findViewById(R.id.print_preview_button);
mPrintPreviewButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- // TODO: Implement
+ mEditor.confirmPreview();
+ // TODO: Implement me
+ Toast.makeText(PrintJobConfigActivity.this,
+ "Stop poking me! Not implemented yet :)",
+ Toast.LENGTH_LONG).show();
}
});
@@ -715,21 +801,108 @@ public class PrintJobConfigActivity extends Activity {
mPrintButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- mPrintConfirmed = true;
- finish();
+ mEditor.confirmPrint();
+ updateUi();
+ mController.update();
}
});
}
- private void updateUiIfNeeded() {
+ public void initialize() {
+ mEditorState = EDITOR_STATE_INITIALIZED;
+ mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION);
+ }
+
+ public boolean isCancelled() {
+ return mEditorState == EDITOR_STATE_CANCELLED;
+ }
+
+ public void cancel() {
+ mEditorState = EDITOR_STATE_CANCELLED;
+ mController.cancel();
+ updateUi();
+ }
+
+ public boolean isDone() {
+ return isPrintConfirmed() || isPreviewConfirmed() || isCancelled();
+ }
+
+ public boolean isPrintConfirmed() {
+ return mEditorState == EDITOR_STATE_CONFIRMED_PRINT;
+ }
+
+ public void confirmPrint() {
+ mEditorState = EDITOR_STATE_CONFIRMED_PRINT;
+ }
+
+ public boolean isPreviewConfirmed() {
+ return mEditorState == EDITOR_STATE_CONFIRMED_PRINT;
+ }
+
+ public void confirmPreview() {
+ mEditorState = EDITOR_STATE_CONFIRMED_PREVIEW;
+ }
+
+ public PageRange[] getRequestedPages() {
+ if (hasErrors()) {
+ return null;
+ }
+ if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
+ List<PageRange> pageRanges = new ArrayList<PageRange>();
+ mStringCommaSplitter.setString(mRangeEditText.getText().toString());
+
+ while (mStringCommaSplitter.hasNext()) {
+ String range = mStringCommaSplitter.next().trim();
+ final int dashIndex = range.indexOf('-');
+ final int fromIndex;
+ final int toIndex;
+
+ if (dashIndex > 0) {
+ fromIndex = Integer.parseInt(range.substring(0, dashIndex)) - 1;
+ toIndex = Integer.parseInt(range.substring(
+ dashIndex + 1, range.length())) - 1;
+ } else {
+ fromIndex = toIndex = Integer.parseInt(range);
+ }
+
+ PageRange pageRange = new PageRange(fromIndex, toIndex);
+ pageRanges.add(pageRange);
+ }
+
+ PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
+ pageRanges.toArray(pageRangesArray);
+
+ return PageRangeUtils.normalize(pageRangesArray);
+ }
+
+ return ALL_PAGES_ARRAY;
+ }
+
+ public void updateUi() {
+ if (isPrintConfirmed() || isPreviewConfirmed() || isCancelled()) {
+ mDestinationSpinner.setEnabled(false);
+ mCopiesEditText.setEnabled(false);
+ mMediaSizeSpinner.setEnabled(false);
+ mColorModeSpinner.setEnabled(false);
+ mOrientationSpinner.setEnabled(false);
+ mRangeOptionsSpinner.setEnabled(false);
+ mRangeEditText.setEnabled(false);
+ mPrintPreviewButton.setEnabled(false);
+ mPrintButton.setEnabled(false);
+ return;
+ }
+
final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
- if (selectedIndex < 0) {
+ if (selectedIndex < 0 || !mDestinationSpinnerAdapter.getItem(
+ selectedIndex).value.hasAllRequiredAttributes()) {
+
// Destination
mDestinationSpinner.setEnabled(false);
- // Copies
- mCopiesEditText.setText("1");
+ mCopiesEditText.removeTextChangedListener(mCopiesTextWatcher);
+ mCopiesEditText.setText(String.valueOf(MIN_COPIES));
+ mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
mCopiesEditText.setEnabled(false);
// Media size
@@ -751,7 +924,11 @@ public class PrintJobConfigActivity extends Activity {
mRangeOptionsSpinner.setOnItemSelectedListener(null);
mRangeOptionsSpinner.setSelection(0);
mRangeOptionsSpinner.setEnabled(false);
+ mRangeTitle.setText(getString(R.string.label_pages,
+ getString(R.string.page_count_unknown)));
+ mRangeEditText.removeTextChangedListener(mRangeTextWatcher);
mRangeEditText.setText("");
+ mRangeEditText.addTextChangedListener(mRangeTextWatcher);
mRangeEditText.setEnabled(false);
mRangeEditText.setVisibility(View.INVISIBLE);
@@ -884,46 +1061,65 @@ public class PrintJobConfigActivity extends Activity {
}
// Range options
- if (mPrintDocumentInfo != null && (mPrintDocumentInfo.getPageCount() > 1
- || mPrintDocumentInfo.getPageCount()
- == PrintDocumentInfo.PAGE_COUNT_UNKNOWN)) {
+ PrintDocumentInfo info = mDocument.info;
+ if (info != null && (info.getPageCount() > 1
+ || info.getPageCount() == PrintDocumentInfo.PAGE_COUNT_UNKNOWN)) {
mRangeOptionsSpinner.setEnabled(true);
if (mRangeOptionsSpinner.getSelectedItemPosition() > 0
&& !mRangeEditText.isEnabled()) {
mRangeEditText.setEnabled(true);
- mRangeEditText.setError("");
mRangeEditText.setVisibility(View.VISIBLE);
mRangeEditText.requestFocus();
InputMethodManager imm = (InputMethodManager)
getSystemService(INPUT_METHOD_SERVICE);
imm.showSoftInput(mRangeEditText, 0);
}
+ final int pageCount = mDocument.info.getPageCount();
+ mRangeTitle.setText(getString(R.string.label_pages,
+ (pageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN)
+ ? getString(R.string.page_count_unknown)
+ : String.valueOf(pageCount)));
} else {
mRangeOptionsSpinner.setOnItemSelectedListener(null);
mRangeOptionsSpinner.setSelection(0);
mRangeOptionsSpinner.setEnabled(false);
+ mRangeTitle.setText(getString(R.string.label_pages,
+ getString(R.string.page_count_unknown)));
mRangeEditText.setEnabled(false);
- mRangeEditText.setText("");
mRangeEditText.setVisibility(View.INVISIBLE);
}
- // Print preview
- mPrintPreviewButton.setEnabled(true);
- if (hasPdfViewer()) {
- mPrintPreviewButton.setText(getString(R.string.print_preview));
+ // Print/Print preview
+ if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1
+ && (TextUtils.isEmpty(mRangeEditText.getText()) || hasErrors()))
+ || (mRangeOptionsSpinner.getSelectedItemPosition() == 0
+ && (!mController.hasPerformedLayout() || hasErrors()))) {
+ mPrintPreviewButton.setEnabled(false);
+ mPrintButton.setEnabled(false);
} else {
- mPrintPreviewButton.setText(getString(R.string.install_for_print_preview));
+ mPrintPreviewButton.setEnabled(true);
+ if (hasPdfViewer()) {
+ mPrintPreviewButton.setText(getString(R.string.print_preview));
+ } else {
+ mPrintPreviewButton.setText(getString(R.string.install_for_print_preview));
+ }
+ mPrintButton.setEnabled(true);
}
- // Print
- mPrintButton.setEnabled(true);
+ // Copies
+ if (mCopiesEditText.getError() == null
+ && TextUtils.isEmpty(mCopiesEditText.getText())) {
+ mCopiesEditText.setText(String.valueOf(MIN_COPIES));
+ mCopiesEditText.selectAll();
+ mCopiesEditText.requestFocus();
+ }
}
// Here is some voodoo to circumvent the weird behavior of AdapterView
// in which a selection listener may get a callback for an event that
// happened before the listener was registered. The reason for that is
// that the selection change is handled on the next layout pass.
- Choreographer.getInstance().postCallback(Choreographer.CALLBACK_TRAVERSAL,
+ Choreographer.getInstance().postCallbackDelayed(Choreographer.CALLBACK_TRAVERSAL,
new Runnable() {
@Override
public void run() {
@@ -932,15 +1128,7 @@ public class PrintJobConfigActivity extends Activity {
mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
}
- }, null);
- }
-
- public PrinterInfo getCurrentPrinter() {
- final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
- if (selectedIndex >= 0) {
- return mDestinationSpinnerAdapter.getItem(selectedIndex).value;
- }
- return null;
+ }, null, Choreographer.getFrameDelay() * 2);
}
public void addPrinters(List<PrinterInfo> addedPrinters) {
@@ -995,8 +1183,317 @@ public class PrintJobConfigActivity extends Activity {
}
}
- private void updatePrintPreview(File file) {
- // TODO: Implement
+ @SuppressWarnings("unchecked")
+ public void updatePrinters(List<PrinterInfo> pritners) {
+ SpinnerItem<PrinterInfo> selectedItem =
+ (SpinnerItem<PrinterInfo>) mDestinationSpinner.getSelectedItem();
+ PrinterId selectedPrinterId = (selectedItem != null)
+ ? selectedItem.value.getId() : null;
+
+ boolean updated = false;
+
+ final int printerCount = pritners.size();
+ for (int i = 0; i < printerCount; i++) {
+ PrinterInfo updatedPrinter = pritners.get(i);
+ final int existingPrinterCount = mDestinationSpinnerAdapter.getCount();
+ for (int j = 0; j < existingPrinterCount; j++) {
+ PrinterInfo existingPrinter = mDestinationSpinnerAdapter.getItem(j).value;
+ if (updatedPrinter.getId().equals(existingPrinter.getId())) {
+ existingPrinter.copyFrom(updatedPrinter);
+ updated = true;
+ if (selectedPrinterId != null
+ && selectedPrinterId.equals(updatedPrinter.getId())) {
+ // The selected printer was updated. We simulate a fake
+ // selection to reuse the normal printer change handling.
+ mOnItemSelectedListener.onItemSelected(mDestinationSpinner,
+ mDestinationSpinner.getSelectedView(),
+ mDestinationSpinner.getSelectedItemPosition(),
+ mDestinationSpinner.getSelectedItemId());
+ // TODO: This will reset the UI to the defaults for the
+ // printer. We may need to revisit this.
+
+ }
+ break;
+ }
+ }
+ }
+ if (updated) {
+ mDestinationSpinnerAdapter.notifyDataSetChanged();
+ }
+ }
+
+ private boolean hasErrors() {
+ return mRangeEditText.getError() != null
+ || mCopiesEditText.getError() != null;
+ }
+
+ private boolean hasPdfViewer() {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setType("application/pdf");
+ return !getPackageManager().queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY).isEmpty();
+ }
+
+ private final class SpinnerItem<T> {
+ final T value;
+ CharSequence label;
+
+ public SpinnerItem(T value, CharSequence label) {
+ this.value = value;
+ this.label = label;
+ }
+
+ public String toString() {
+ return label.toString();
+ }
+ }
+
+ private final class DestinationAdapter extends ArrayAdapter<SpinnerItem<PrinterInfo>> {
+
+ public DestinationAdapter() {
+ super( PrintJobConfigActivity.this, R.layout.spinner_dropdown_item);
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView,
+ ViewGroup parent) {
+ return getView(position, convertView, parent);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = getLayoutInflater().inflate(
+ R.layout.spinner_dropdown_item, parent, false);
+ }
+
+ PrinterInfo printerInfo = getItem(position).value;
+ TextView title = (TextView) convertView.findViewById(R.id.title);
+ title.setText(printerInfo.getLabel());
+
+ try {
+ TextView subtitle = (TextView)
+ convertView.findViewById(R.id.subtitle);
+ PackageManager pm = getPackageManager();
+ PackageInfo packageInfo = pm.getPackageInfo(
+ printerInfo.getId().getService().getPackageName(), 0);
+ subtitle.setText(packageInfo.applicationInfo.loadLabel(pm));
+ subtitle.setVisibility(View.VISIBLE);
+ } catch (NameNotFoundException nnfe) {
+ /* ignore */
+ }
+
+ return convertView;
+ }
+ }
+ }
+
+ private static final class PrinterDiscoveryObserver extends IPrinterDiscoveryObserver.Stub {
+ private static final int MSG_ON_PRINTERS_ADDED = 1;
+ private static final int MSG_ON_PRINTERS_REMOVED = 2;
+ private static final int MSG_ON_PRINTERS_UPDATED = 3;
+
+ private Handler mHandler;
+ private Editor mEditor;
+
+ @SuppressWarnings("unchecked")
+ public PrinterDiscoveryObserver(Editor editor, Looper looper) {
+ mEditor = editor;
+ mHandler = new Handler(looper, null, true) {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_ON_PRINTERS_ADDED: {
+ List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
+ mEditor.addPrinters(printers);
+ } break;
+
+ case MSG_ON_PRINTERS_REMOVED: {
+ List<PrinterId> printerIds = (List<PrinterId>) message.obj;
+ mEditor.removePrinters(printerIds);
+ } break;
+
+ case MSG_ON_PRINTERS_UPDATED: {
+ List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
+ mEditor.updatePrinters(printers);
+ } break;
+ }
+ }
+ };
+ }
+
+ @Override
+ public void onPrintersAdded(List<PrinterInfo> printers) {
+ synchronized (this) {
+ if (mHandler != null) {
+ mHandler.obtainMessage(MSG_ON_PRINTERS_ADDED, printers)
+ .sendToTarget();
+ }
+ }
+ }
+
+ @Override
+ public void onPrintersRemoved(List<PrinterId> printers) {
+ synchronized (this) {
+ if (mHandler != null) {
+ mHandler.obtainMessage(MSG_ON_PRINTERS_REMOVED, printers)
+ .sendToTarget();
+ }
+ }
+ }
+
+ @Override
+ public void onPrintersUpdated(List<PrinterInfo> printers) {
+ synchronized (this) {
+ if (mHandler != null) {
+ mHandler.obtainMessage(MSG_ON_PRINTERS_UPDATED, printers)
+ .sendToTarget();
+ }
+ }
+ }
+
+ public void destroy() {
+ synchronized (this) {
+ mHandler = null;
+ mEditor = null;
+ }
+ }
+ }
+
+ /**
+ * An instance of this class class is intended to be the first focusable
+ * in a layout to which the system automatically gives focus. It performs
+ * some voodoo to avoid the first tap on it to start an edit mode, rather
+ * to bring up the IME, i.e. to get the behavior as if the view was not
+ * focused.
+ */
+ public static final class CustomEditText extends EditText {
+ private boolean mClickedBeforeFocus;
+ private CharSequence mError;
+
+ public CustomEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean performClick() {
+ super.performClick();
+ if (isFocused() && !mClickedBeforeFocus) {
+ clearFocus();
+ requestFocus();
+ }
+ mClickedBeforeFocus = true;
+ return true;
+ }
+
+ @Override
+ public CharSequence getError() {
+ return mError;
+ }
+
+ @Override
+ public void setError(CharSequence error, Drawable icon) {
+ setCompoundDrawables(null, null, icon, null);
+ mError = error;
+ }
+
+ protected void onFocusChanged(boolean gainFocus, int direction,
+ Rect previouslyFocusedRect) {
+ if (!gainFocus) {
+ mClickedBeforeFocus = false;
+ }
+ super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ }
+ }
+
+ private static final class Document {
+ public PrintDocumentInfo info;
+ public PageRange[] pages;
+ }
+
+ private static final class PageRangeUtils {
+
+ private static final Comparator<PageRange> sComparator = new Comparator<PageRange>() {
+ @Override
+ public int compare(PageRange lhs, PageRange rhs) {
+ return lhs.getStart() - rhs.getStart();
+ }
+ };
+
+ private PageRangeUtils() {
+ throw new UnsupportedOperationException();
+ }
+
+ public static boolean contains(PageRange[] ourPageRanges, PageRange[] otherPageRanges) {
+ if (ourPageRanges == null || otherPageRanges == null) {
+ return false;
+ }
+
+ otherPageRanges = normalize(otherPageRanges);
+
+ int otherPageIdx = 0;
+ final int myPageCount = ourPageRanges.length;
+ final int otherPageCount = otherPageRanges.length;
+ for (int i= 0; i < myPageCount; i++) {
+ PageRange myPage = ourPageRanges[i];
+ for (; otherPageIdx < otherPageCount; otherPageIdx++) {
+ PageRange otherPage = otherPageRanges[otherPageIdx];
+ if (otherPage.getStart() > myPage.getStart()) {
+ break;
+ }
+ if ((otherPage.getStart() < myPage.getStart()
+ && otherPage.getEnd() > myPage.getStart())
+ || (otherPage.getEnd() > myPage.getEnd()
+ && otherPage.getStart() < myPage.getEnd())
+ || (otherPage.getEnd() < myPage.getStart())) {
+ return false;
+ }
+ }
+ }
+ if (otherPageIdx < otherPageCount) {
+ return false;
+ }
+ return true;
+ }
+
+ public static PageRange[] normalize(PageRange[] pageRanges) {
+ if (pageRanges == null) {
+ return null;
+ }
+ final int oldPageCount = pageRanges.length;
+ if (oldPageCount <= 1) {
+ return pageRanges;
+ }
+ Arrays.sort(pageRanges, sComparator);
+ int newRangeCount = 0;
+ for (int i = 0; i < oldPageCount - 1; i++) {
+ newRangeCount++;
+ PageRange currentRange = pageRanges[i];
+ PageRange nextRange = pageRanges[i + 1];
+ if (currentRange.getEnd() >= nextRange.getStart()) {
+ newRangeCount--;
+ pageRanges[i] = null;
+ pageRanges[i + 1] = new PageRange(currentRange.getStart(),
+ nextRange.getEnd());
+ }
+ }
+ if (newRangeCount == oldPageCount) {
+ return pageRanges;
+ }
+ return Arrays.copyOfRange(pageRanges, oldPageCount - newRangeCount,
+ oldPageCount - 1);
+ }
+
+ public static void offsetStart(PageRange[] pageRanges, int offset) {
+ if (offset == 0) {
+ return;
+ }
+ final int pageRangeCount = pageRanges.length;
+ for (int i = 0; i < pageRangeCount; i++) {
+ final int start = pageRanges[i].getStart() + offset;
+ final int end = pageRanges[i].getEnd() + offset;
+ pageRanges[i] = new PageRange(start, end);
+ }
}
}
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
index 53ae1459ca0a..870bfffda621 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
@@ -19,6 +19,9 @@ package com.android.printspooler;
import android.content.ComponentName;
import android.content.Context;
import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.print.IPrintClient;
@@ -39,6 +42,7 @@ import android.util.Log;
import android.util.Slog;
import android.util.Xml;
+import com.android.internal.os.SomeArgs;
import com.android.internal.util.FastXmlSerializer;
import libcore.io.IoUtils;
@@ -59,9 +63,9 @@ import java.util.Map;
public class PrintSpooler {
- private static final String LOG_TAG = PrintSpooler.class.getSimpleName();
+ private static final String LOG_TAG = "PrintSpooler";
- private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = false;
+ private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = true;
private static final boolean DEBUG_PERSISTENCE = true;
@@ -81,6 +85,8 @@ public class PrintSpooler {
private final PersistenceManager mPersistanceManager;
+ private final Handler mHandler;
+
private final Context mContext;
public IPrintSpoolerClient mClient;
@@ -97,6 +103,7 @@ public class PrintSpooler {
private PrintSpooler(Context context) {
mContext = context;
mPersistanceManager = new PersistenceManager(context);
+ mHandler = new MyHandler(context.getMainLooper());
}
public void setCleint(IPrintSpoolerClient client) {
@@ -111,37 +118,36 @@ public class PrintSpooler {
}
}
- public void startPrinterDiscovery(IPrinterDiscoveryObserver observer) {
- IPrintSpoolerClient client = null;
+ public void onReqeustUpdatePrinters(List<PrinterId> printers) {
synchronized (mLock) {
- client = mClient;
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = mClient;
+ args.arg2 = printers;
+ mHandler.obtainMessage(MyHandler.MSG_REQUEST_UPDATE_PRINTERS,
+ args).sendToTarget();
}
- if (client != null) {
- try {
- client.onStartPrinterDiscovery(observer);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error notifying start printer discovery.", re);
- }
+ }
+
+ public void startPrinterDiscovery(IPrinterDiscoveryObserver observer) {
+ synchronized (mLock) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = mClient;
+ args.arg2 = observer;
+ mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_DISCOVERY,
+ args).sendToTarget();
}
}
public void stopPrinterDiscovery() {
- IPrintSpoolerClient client = null;
synchronized (mLock) {
- client = mClient;
- }
- if (client != null) {
- try {
- client.onStopPrinterDiscovery();
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error notifying stop printer discovery.", re);
- }
+ mHandler.obtainMessage(MyHandler.MSG_STOP_PRINTER_DISCOVERY,
+ mClient).sendToTarget();
}
}
public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, int state, int appId) {
+ List<PrintJobInfo> foundPrintJobs = null;
synchronized (mLock) {
- List<PrintJobInfo> foundPrintJobs = null;
final int printJobCount = mPrintJobs.size();
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = mPrintJobs.get(i);
@@ -162,8 +168,8 @@ public class PrintSpooler {
foundPrintJobs.add(printJob);
}
}
- return foundPrintJobs;
}
+ return foundPrintJobs;
}
public PrintJobInfo getPrintJobInfo(int printJobId, int appId) {
@@ -172,11 +178,12 @@ public class PrintSpooler {
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = mPrintJobs.get(i);
if (printJob.getId() == printJobId
- && (appId == PrintManager.APP_ID_ANY || appId == printJob.getAppId())) {
+ && (appId == PrintManager.APP_ID_ANY
+ || appId == printJob.getAppId())) {
return printJob;
}
}
- return null;
+ return null;
}
}
@@ -217,7 +224,7 @@ public class PrintSpooler {
Map<ComponentName, List<PrintJobInfo>> activeJobsPerServiceMap =
new HashMap<ComponentName, List<PrintJobInfo>>();
- synchronized(mLock) {
+ synchronized (mLock) {
if (mClient == null) {
throw new IllegalStateException("Client cannot be null.");
}
@@ -265,16 +272,25 @@ public class PrintSpooler {
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = printJobs.get(i);
if (printJob.getState() == PrintJobInfo.STATE_QUEUED) {
- callOnPrintJobQueuedQuietly(client, printJob);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = client;
+ args.arg2 = new PrintJobInfo(printJob);
+ mHandler.obtainMessage(MyHandler.MSG_PRINT_JOB_QUEUED,
+ args).sendToTarget();
}
}
} else {
- callOnAllPrintJobsForServiceHandledQuietly(client, service);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = client;
+ args.arg2 = service;
+ mHandler.obtainMessage(MyHandler.MSG_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED,
+ args).sendToTarget();
}
}
if (allPrintJobsHandled) {
- callOnAllPrintJobsHandledQuietly(client);
+ mHandler.obtainMessage(MyHandler.MSG_ALL_PRINT_JOBS_HANDLED,
+ client).sendToTarget();
}
}
@@ -297,37 +313,43 @@ public class PrintSpooler {
return false;
}
- @SuppressWarnings("resource")
- public boolean writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
+ public void writePrintJobData(final ParcelFileDescriptor fd, final int printJobId) {
+ final PrintJobInfo printJob;
synchronized (mLock) {
- FileInputStream in = null;
- FileOutputStream out = null;
- try {
- PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- if (printJob != null) {
- File file = generateFileForPrintJob(printJobId);
- in = new FileInputStream(file);
- out = new FileOutputStream(fd.getFileDescriptor());
+ printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ }
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ FileInputStream in = null;
+ FileOutputStream out = null;
+ try {
+ if (printJob != null) {
+ File file = generateFileForPrintJob(printJobId);
+ in = new FileInputStream(file);
+ out = new FileOutputStream(fd.getFileDescriptor());
+ }
final byte[] buffer = new byte[8192];
while (true) {
final int readByteCount = in.read(buffer);
if (readByteCount < 0) {
- return true;
+ return null;
}
out.write(buffer, 0, readByteCount);
}
+ } catch (FileNotFoundException fnfe) {
+ Log.e(LOG_TAG, "Error writing print job data!", fnfe);
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Error writing print job data!", ioe);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ IoUtils.closeQuietly(fd);
}
- } catch (FileNotFoundException fnfe) {
- Log.e(LOG_TAG, "Error writing print job data!", fnfe);
- } catch (IOException ioe) {
- Log.e(LOG_TAG, "Error writing print job data!", ioe);
- } finally {
- IoUtils.closeQuietly(in);
- IoUtils.closeQuietly(out);
- IoUtils.closeQuietly(fd);
+ Log.i(LOG_TAG, "[END WRITE]");
+ return null;
}
- }
- return false;
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
}
public File generateFileForPrintJob(int printJobId) {
@@ -354,28 +376,24 @@ public class PrintSpooler {
public boolean setPrintJobState(int printJobId, int state) {
boolean success = false;
- boolean allPrintJobsHandled = false;
- boolean allPrintJobsForServiceHandled = false;
-
- IPrintSpoolerClient client = null;
- PrintJobInfo queuedPrintJob = null;
- PrintJobInfo removedPrintJob = null;
-
synchronized (mLock) {
if (mClient == null) {
throw new IllegalStateException("Client cannot be null.");
}
- client = mClient;
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null && printJob.getState() < state) {
success = true;
printJob.setState(state);
+
+ if (DEBUG_PRINT_JOB_LIFECYCLE) {
+ Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
+ }
+
// TODO: Update notifications.
switch (state) {
case PrintJobInfo.STATE_COMPLETED:
case PrintJobInfo.STATE_CANCELED: {
- removedPrintJob = printJob;
removePrintJobLocked(printJob);
// No printer means creation of a print job was cancelled,
@@ -387,83 +405,46 @@ public class PrintSpooler {
return true;
}
- allPrintJobsHandled = !hasActivePrintJobsLocked();
- allPrintJobsForServiceHandled = !hasActivePrintJobsForServiceLocked(
- printerId.getService());
+ ComponentName service = printerId.getService();
+ if (!hasActivePrintJobsForServiceLocked(service)) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = mClient;
+ args.arg2 = service;
+ mHandler.obtainMessage(
+ MyHandler.MSG_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED,
+ args).sendToTarget();
+ }
+
+ if (!hasActivePrintJobsLocked()) {
+ mHandler.obtainMessage(MyHandler.MSG_ALL_PRINT_JOBS_HANDLED,
+ mClient).sendToTarget();
+ }
} break;
case PrintJobInfo.STATE_QUEUED: {
- queuedPrintJob = new PrintJobInfo(printJob);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = mClient;
+ args.arg2 = new PrintJobInfo(printJob);
+ mHandler.obtainMessage(MyHandler.MSG_PRINT_JOB_QUEUED,
+ args).sendToTarget();
} break;
}
- if (DEBUG_PRINT_JOB_LIFECYCLE) {
- Slog.i(LOG_TAG, "[STATUS CHANGED] " + printJob);
+
+ if (shouldPersistPrintJob(printJob)) {
+ mPersistanceManager.writeStateLocked();
}
- mPersistanceManager.writeStateLocked();
}
}
- if (queuedPrintJob != null) {
- callOnPrintJobQueuedQuietly(client, queuedPrintJob);
- }
-
- if (allPrintJobsForServiceHandled) {
- callOnAllPrintJobsForServiceHandledQuietly(client,
- removedPrintJob.getPrinterId().getService());
- }
-
- if (allPrintJobsHandled) {
- callOnAllPrintJobsHandledQuietly(client);
- }
-
return success;
}
- private void callOnPrintJobQueuedQuietly(IPrintSpoolerClient client,
- PrintJobInfo printJob) {
- try {
- client.onPrintJobQueued(printJob);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error notify for a queued print job.", re);
- }
- }
-
- private void callOnAllPrintJobsForServiceHandledQuietly(IPrintSpoolerClient client,
- ComponentName service) {
- try {
- client.onAllPrintJobsForServiceHandled(service);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error notify for all print jobs per service handled.", re);
- }
- }
-
- private void callOnAllPrintJobsHandledQuietly(final IPrintSpoolerClient client) {
- // This has to run on the tread that is persisting the current state
- // since this call may result in the system unbinding from the spooler
- // and as a result the spooler process may get killed before the write
- // completes.
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- try {
- client.onAllPrintJobsHandled();
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error notify for all print job handled.", re);
- }
- return null;
- }
- }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
- }
-
private boolean hasActivePrintJobsLocked() {
final int printJobCount = mPrintJobs.size();
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = mPrintJobs.get(i);
- switch (printJob.getState()) {
- case PrintJobInfo.STATE_QUEUED:
- case PrintJobInfo.STATE_STARTED: {
- return true;
- }
+ if (!isActiveState(printJob.getState())) {
+ return true;
}
}
return false;
@@ -473,72 +454,89 @@ public class PrintSpooler {
final int printJobCount = mPrintJobs.size();
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = mPrintJobs.get(i);
- switch (printJob.getState()) {
- case PrintJobInfo.STATE_QUEUED:
- case PrintJobInfo.STATE_STARTED: {
- if (printJob.getPrinterId().getService().equals(service)) {
- return true;
- }
- } break;
+ if (!isActiveState(printJob.getState())
+ && printJob.getPrinterId().getService().equals(service)) {
+ return true;
}
}
return false;
}
+ private static boolean isActiveState(int printJobState) {
+ return printJobState != PrintJobInfo.STATE_CREATED
+ || printJobState != PrintJobInfo.STATE_QUEUED
+ || printJobState != PrintJobInfo.STATE_STARTED;
+ }
+
public boolean setPrintJobTag(int printJobId, String tag) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
+ String printJobTag = printJob.getTag();
+ if (printJobTag == null) {
+ if (tag == null) {
+ return false;
+ }
+ } else if (printJobTag.equals(tag)) {
+ return false;
+ }
printJob.setTag(tag);
- mPersistanceManager.writeStateLocked();
+ if (shouldPersistPrintJob(printJob)) {
+ mPersistanceManager.writeStateLocked();
+ }
return true;
}
}
return false;
}
- public final boolean setPrintJobPrintDocumentInfo(int printJobId, PrintDocumentInfo info) {
+ public void setPrintJobCopiesNoPersistence(int printJobId, int copies) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setCopies(copies);
+ }
+ }
+ }
+
+ public void setPrintJobPrintDocumentInfoNoPersistence(int printJobId, PrintDocumentInfo info) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
printJob.setDocumentInfo(info);
- mPersistanceManager.writeStateLocked();
- return true;
}
}
- return false;
}
- public void setPrintJobAttributes(int printJobId, PrintAttributes attributes) {
+ public void setPrintJobAttributesNoPersistence(int printJobId, PrintAttributes attributes) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
printJob.setAttributes(attributes);
- mPersistanceManager.writeStateLocked();
}
}
}
- public void setPrintJobPrinterId(int printJobId, PrinterId printerId) {
+ public void setPrintJobPrinterIdNoPersistence(int printJobId, PrinterId printerId) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
printJob.setPrinterId(printerId);
- mPersistanceManager.writeStateLocked();
}
}
}
- public boolean setPrintJobPages(int printJobId, PageRange[] pages) {
+ public void setPrintJobPagesNoPersistence(int printJobId, PageRange[] pages) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
if (printJob != null) {
printJob.setPages(pages);
- mPersistanceManager.writeStateLocked();
- return true;
}
}
- return false;
+ }
+
+ private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
+ return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
}
private final class PersistenceManager {
@@ -558,6 +556,7 @@ public class PrintSpooler {
private static final String ATTR_APP_ID = "appId";
private static final String ATTR_USER_ID = "userId";
private static final String ATTR_TAG = "tag";
+ private static final String ATTR_COPIES = "copies";
private static final String TAG_MEDIA_SIZE = "mediaSize";
private static final String TAG_RESOLUTION = "resolution";
@@ -620,6 +619,9 @@ public class PrintSpooler {
}
private void doWriteStateLocked() {
+ if (DEBUG_PERSISTENCE) {
+ Log.i(LOG_TAG, "[PERSIST START]");
+ }
FileOutputStream out = null;
try {
out = mStatePersistFile.startWrite();
@@ -652,6 +654,7 @@ public class PrintSpooler {
if (tag != null) {
serializer.attribute(null, ATTR_TAG, tag);
}
+ serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
PrinterId printerId = printJob.getPrinterId();
if (printerId != null) {
@@ -775,6 +778,9 @@ public class PrintSpooler {
serializer.endTag(null, TAG_SPOOLER);
serializer.endDocument();
mStatePersistFile.finishWrite(out);
+ if (DEBUG_PERSISTENCE) {
+ Log.i(LOG_TAG, "[PERSIST END]");
+ }
} catch (IOException e) {
Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
mStatePersistFile.failWrite(out);
@@ -861,6 +867,8 @@ public class PrintSpooler {
printJob.setUserId(userId);
String tag = parser.getAttributeValue(null, ATTR_TAG);
printJob.setTag(tag);
+ String copies = parser.getAttributeValue(null, ATTR_TAG);
+ printJob.setCopies(Integer.parseInt(copies));
parser.next();
@@ -892,7 +900,9 @@ public class PrintSpooler {
parser.next();
}
if (pageRanges != null) {
- printJob.setPages((PageRange[]) pageRanges.toArray());
+ PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
+ pageRanges.toArray(pageRangesArray);
+ printJob.setPages(pageRangesArray);
}
skipEmptyTextTags(parser);
@@ -1054,4 +1064,108 @@ public class PrintSpooler {
return true;
}
}
+
+ private final class MyHandler extends Handler {
+ public static final int MSG_START_PRINTER_DISCOVERY = 1;
+ public static final int MSG_STOP_PRINTER_DISCOVERY = 2;
+ public static final int MSG_PRINT_JOB_QUEUED = 3;
+ public static final int MSG_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 4;
+ public static final int MSG_ALL_PRINT_JOBS_HANDLED = 5;
+ public static final int MSG_REQUEST_UPDATE_PRINTERS = 6;
+
+ public MyHandler(Looper looper) {
+ super(looper, null, false);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_START_PRINTER_DISCOVERY: {
+ SomeArgs args = (SomeArgs) message.obj;
+ IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
+ IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) args.arg2;
+ args.recycle();
+ if (client != null) {
+ try {
+ client.onStartPrinterDiscovery(observer);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error notifying start printer discovery.", re);
+ }
+ }
+ } break;
+
+ case MSG_STOP_PRINTER_DISCOVERY: {
+ IPrintSpoolerClient client = (IPrintSpoolerClient) message.obj;
+ if (client != null) {
+ try {
+ client.onStopPrinterDiscovery();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error notifying stop printer discovery.", re);
+ }
+ }
+ } break;
+
+ case MSG_PRINT_JOB_QUEUED: {
+ SomeArgs args = (SomeArgs) message.obj;
+ IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
+ PrintJobInfo printJob = (PrintJobInfo) args.arg2;
+ args.recycle();
+ if (client != null) {
+ try {
+ client.onPrintJobQueued(printJob);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error notify for a queued print job.", re);
+ }
+ }
+ } break;
+
+ case MSG_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED: {
+ SomeArgs args = (SomeArgs) message.obj;
+ IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
+ ComponentName service = (ComponentName) args.arg2;
+ args.recycle();
+ if (client != null) {
+ try {
+ client.onAllPrintJobsForServiceHandled(service);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error notify for all print jobs per service"
+ + " handled.", re);
+ }
+ }
+ } break;
+
+ case MSG_ALL_PRINT_JOBS_HANDLED: {
+ final IPrintSpoolerClient client = (IPrintSpoolerClient) message.obj;
+ // This has to run on the tread that is persisting the current state
+ // since this call may result in the system unbinding from the spooler
+ // and as a result the spooler process may get killed before the write
+ // completes.
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ client.onAllPrintJobsHandled();
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error notify for all print job handled.", re);
+ }
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ } break;
+
+ case MSG_REQUEST_UPDATE_PRINTERS: {
+ SomeArgs args = (SomeArgs) message.obj;
+ IPrintSpoolerClient client = (IPrintSpoolerClient) args.arg1;
+ List<PrinterId> printerIds = (List<PrinterId>) args.arg2;
+ args.recycle();
+ try {
+ client.onRequestUpdatePrinters(printerIds);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error requesting to update pritners.", re);
+ }
+ } break;
+ }
+ }
+ }
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
index 26d2a3329a66..5ff2aa6100f3 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
@@ -114,12 +114,11 @@ public final class PrintSpoolerService extends Service {
attributes, appId);
if (printJob != null) {
Intent intent = mStartPrintJobConfigActivityIntent;
- intent.putExtra(PrintJobConfigActivity.EXTRA_PRINTABLE,
+ intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_DOCUMENT_ADAPTER,
printAdapter.asBinder());
- intent.putExtra(PrintJobConfigActivity.EXTRA_APP_ID, appId);
intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_JOB_ID,
printJob.getId());
- intent.putExtra(PrintJobConfigActivity.EXTRA_ATTRIBUTES, attributes);
+ intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_ATTRIBUTES, attributes);
IntentSender sender = PendingIntent.getActivity(
PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT
diff --git a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java
index 25bb37c51710..4006a5a746bc 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java
@@ -17,8 +17,8 @@
package com.android.printspooler;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Bundle;
-import android.os.ICancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.print.ILayoutResultCallback;
@@ -26,11 +26,7 @@ import android.print.IPrintDocumentAdapter;
import android.print.IWriteResultCallback;
import android.print.PageRange;
import android.print.PrintAttributes;
-import android.print.PrintDocumentAdapter.LayoutResultCallback;
-import android.print.PrintDocumentAdapter.WriteResultCallback;
-import android.print.PrintDocumentInfo;
import android.util.Log;
-import android.util.Slog;
import libcore.io.IoUtils;
@@ -40,8 +36,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
/**
* This class represents a remote print document adapter instance.
@@ -49,461 +43,99 @@ import java.util.List;
final class RemotePrintDocumentAdapter {
private static final String LOG_TAG = "RemotePrintDocumentAdapter";
- private static final boolean DEBUG = true;
-
- public static final int STATE_INITIALIZED = 0;
- public static final int STATE_START_COMPLETED = 1;
- public static final int STATE_LAYOUT_STARTED = 2;
- public static final int STATE_LAYOUT_COMPLETED = 3;
- public static final int STATE_WRITE_STARTED = 4;
- public static final int STATE_WRITE_COMPLETED = 5;
- public static final int STATE_FINISH_COMPLETED = 6;
- public static final int STATE_FAILED = 7;
-
- private final Object mLock = new Object();
-
- private final List<QueuedAsyncTask> mTaskQueue = new ArrayList<QueuedAsyncTask>();
+ private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
private final IPrintDocumentAdapter mRemoteInterface;
private final File mFile;
- private int mState = STATE_INITIALIZED;
-
public RemotePrintDocumentAdapter(IPrintDocumentAdapter printAdatper, File file) {
mRemoteInterface = printAdatper;
mFile = file;
}
- public File getFile() {
+ public void start() {
if (DEBUG) {
- Log.i(LOG_TAG, "getFile()");
+ Log.i(LOG_TAG, "start()");
}
- synchronized (mLock) {
- if (mState != STATE_WRITE_COMPLETED
- && mState != STATE_FINISH_COMPLETED) {
- throw new IllegalStateException("Write not completed");
- }
- return mFile;
+ try {
+ mRemoteInterface.start();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling start()", re);
}
}
- public void start() {
- QueuedAsyncTask task = new QueuedAsyncTask() {
- @Override
- protected Void doInBackground(Void... params) {
- if (DEBUG) {
- Log.i(LOG_TAG, "start()");
- }
- synchronized (mLock) {
- if (mState != STATE_INITIALIZED) {
- throw new IllegalStateException("Invalid state: " + mState);
- }
- }
- try {
- mRemoteInterface.start();
- synchronized (mLock) {
- mState = STATE_START_COMPLETED;
- }
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error reading file", re);
- }
- return null;
- }
-
- @Override
- public void cancel() {
- /* cannot be cancelled */
- }
- };
- synchronized (mLock) {
- mTaskQueue.add(task);
- }
- task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
- }
-
public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
- LayoutResultCallback callback, Bundle metadata) {
- LayoutAsyncTask task = new LayoutAsyncTask(oldAttributes, newAttributes, callback,
- metadata);
- synchronized (mLock) {
- mTaskQueue.add(task);
+ ILayoutResultCallback callback, Bundle metadata, int sequence) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "layout()");
}
- task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
- }
-
- public void write(List<PageRange> pages, WriteResultCallback callback) {
- WriteAsyncTask task = new WriteAsyncTask(pages, callback);
- synchronized (mLock) {
- mTaskQueue.add(task);
+ try {
+ mRemoteInterface.layout(oldAttributes, newAttributes, callback, metadata, sequence);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling layout()", re);
}
- task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}
- public void cancel() {
- synchronized (mLock) {
- final int taskCount = mTaskQueue.size();
- for (int i = taskCount - 1; i >= 0; i--) {
- mTaskQueue.remove(i).cancel();
- }
+ public void write(final PageRange[] pages, final IWriteResultCallback callback,
+ final int sequence) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "write()");
}
- }
-
- public void finish() {
- QueuedAsyncTask task = new QueuedAsyncTask() {
+ new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
- if (DEBUG) {
- Log.i(LOG_TAG, "finish()");
- }
- synchronized (mLock) {
- if (mState < STATE_START_COMPLETED) {
- return null;
- }
- }
+ InputStream in = null;
+ OutputStream out = null;
+ ParcelFileDescriptor source = null;
+ ParcelFileDescriptor sink = null;
try {
- mRemoteInterface.finish();
- synchronized (mLock) {
- mState = STATE_FINISH_COMPLETED;
- }
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error reading file", re);
- mState = STATE_FAILED;
- }
- return null;
- }
-
- @Override
- public void cancel() {
- /* cannot be cancelled */
- }
- };
- synchronized (mLock) {
- mTaskQueue.add(task);
- }
- task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
- }
-
- private abstract class QueuedAsyncTask extends AsyncTask<Void, Void, Void> {
- public void cancel() {
- super.cancel(true);
- }
- }
-
- private final class LayoutAsyncTask extends QueuedAsyncTask {
-
- private final PrintAttributes mOldAttributes;
-
- private final PrintAttributes mNewAttributes;
-
- private final LayoutResultCallback mCallback;
-
- private final Bundle mMetadata;
-
- private final ILayoutResultCallback mILayoutResultCallback =
- new ILayoutResultCallback.Stub() {
- @Override
- public void onLayoutStarted(ICancellationSignal cancellationSignal) {
- if (DEBUG) {
- Log.i(LOG_TAG, "onLayoutStarted()");
- }
- synchronized (mLock) {
- mCancellationSignal = cancellationSignal;
- if (isCancelled()) {
- cancelSignalQuietlyLocked();
- }
- }
- }
-
- @Override
- public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
- if (DEBUG) {
- Log.i(LOG_TAG, "onLayoutFinished()");
- }
- final boolean cancelled;
- synchronized (mLock) {
- cancelled = isCancelled();
- mCancellationSignal = null;
- mState = STATE_LAYOUT_COMPLETED;
- mTaskQueue.remove(this);
- mLock.notifyAll();
- }
- if (!cancelled) {
- mCallback.onLayoutFinished(info, changed);
- }
- }
-
- @Override
- public void onLayoutFailed(CharSequence error) {
- if (DEBUG) {
- Log.i(LOG_TAG, "onLayoutFailed()");
- }
- final boolean cancelled;
- synchronized (mLock) {
- cancelled = isCancelled();
- mCancellationSignal = null;
- mState = STATE_LAYOUT_COMPLETED;
- mTaskQueue.remove(this);
- mLock.notifyAll();
- }
- if (!cancelled) {
- mCallback.onLayoutFailed(error);
- }
- }
- };
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ source = pipe[0];
+ sink = pipe[1];
- private ICancellationSignal mCancellationSignal;
+ in = new FileInputStream(source.getFileDescriptor());
+ out = new FileOutputStream(mFile);
- public LayoutAsyncTask(PrintAttributes oldAttributes, PrintAttributes newAttributes,
- LayoutResultCallback callback, Bundle metadata) {
- mOldAttributes = oldAttributes;
- mNewAttributes = newAttributes;
- mCallback = callback;
- mMetadata = metadata;
- }
+ // Async call to initiate the other process writing the data.
+ mRemoteInterface.write(pages, sink, callback, sequence);
- @Override
- public void cancel() {
- synchronized (mLock) {
- throwIfCancelledLocked();
- cancelSignalQuietlyLocked();
- }
- super.cancel();
- }
+ // Close the source. It is now held by the client.
+ sink.close();
+ sink = null;
- @Override
- protected Void doInBackground(Void... params) {
- synchronized (mLock) {
- if (DEBUG) {
- Log.i(LOG_TAG, "layout()");
- }
- if (mState != STATE_START_COMPLETED
- && mState != STATE_LAYOUT_COMPLETED
- && mState != STATE_WRITE_COMPLETED) {
- throw new IllegalStateException("Invalid state: " + mState);
- }
- mState = STATE_LAYOUT_STARTED;
- }
- try {
- mRemoteInterface.layout(mOldAttributes, mNewAttributes,
- mILayoutResultCallback, mMetadata);
- synchronized (mLock) {
+ // Read the data.
+ final byte[] buffer = new byte[8192];
while (true) {
- if (mState == STATE_LAYOUT_COMPLETED) {
+ final int readByteCount = in.read(buffer);
+ if (readByteCount < 0) {
break;
}
- try {
- mLock.wait();
- } catch (InterruptedException ie) {
- /* ignore */
- }
+ out.write(buffer, 0, readByteCount);
}
- }
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error calling layout", re);
- mState = STATE_FAILED;
- mTaskQueue.remove(this);
- notifyLayoutFailedQuietly();
- }
- return null;
- }
-
- private void cancelSignalQuietlyLocked() {
- if (mCancellationSignal != null) {
- try {
- mCancellationSignal.cancel();
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error cancelling layout", re);
- notifyLayoutFailedQuietly();
+ Log.e(LOG_TAG, "Error calling write()", re);
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Error calling write()", ioe);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ IoUtils.closeQuietly(sink);
+ IoUtils.closeQuietly(source);
}
+ return null;
}
- }
-
- public void notifyLayoutFailedQuietly() {
- try {
- mILayoutResultCallback.onLayoutFailed(null);
- } catch (RemoteException re) {
- /* ignore */
- }
- }
-
- private void throwIfCancelledLocked() {
- if (isCancelled()) {
- throw new IllegalStateException("Already cancelled");
- }
- }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
}
- private final class WriteAsyncTask extends QueuedAsyncTask {
-
- private final List<PageRange> mPages;
-
- private final WriteResultCallback mCallback;
-
- private final IWriteResultCallback mIWriteResultCallback =
- new IWriteResultCallback.Stub() {
- @Override
- public void onWriteStarted(ICancellationSignal cancellationSignal) {
- if (DEBUG) {
- Log.i(LOG_TAG, "onWriteStarted()");
- }
- synchronized (mLock) {
- mCancellationSignal = cancellationSignal;
- if (isCancelled()) {
- cancelSignalQuietlyLocked();
- }
- }
- }
-
- @Override
- public void onWriteFinished(List<PageRange> pages) {
- if (DEBUG) {
- Log.i(LOG_TAG, "onWriteFinished()");
- }
- synchronized (mLock) {
- mCancellationSignal = null;
- mState = STATE_WRITE_COMPLETED;
- mTaskQueue.remove(this);
- mLock.notifyAll();
- }
- mCallback.onWriteFinished(pages);
- }
-
- @Override
- public void onWriteFailed(CharSequence error) {
- if (DEBUG) {
- Log.i(LOG_TAG, "onWriteFailed()");
- }
- synchronized (mLock) {
- mCancellationSignal = null;
- mState = STATE_WRITE_COMPLETED;
- mTaskQueue.remove(this);
- mLock.notifyAll();
- }
- Slog.e(LOG_TAG, "Error writing print document: " + error);
- mCallback.onWriteFailed(error);
- }
- };
-
- private ICancellationSignal mCancellationSignal;
-
- private Thread mWriteThread;
-
- public WriteAsyncTask(List<PageRange> pages, WriteResultCallback callback) {
- mPages = pages;
- mCallback = callback;
- }
-
- @Override
- public void cancel() {
- synchronized (mLock) {
- throwIfCancelledLocked();
- cancelSignalQuietlyLocked();
- mWriteThread.interrupt();
- }
- super.cancel();
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- if (DEBUG) {
- Log.i(LOG_TAG, "write()");
- }
- synchronized (mLock) {
- if (mState != STATE_LAYOUT_COMPLETED
- && mState != STATE_WRITE_COMPLETED) {
- throw new IllegalStateException("Invalid state: " + mState);
- }
- mState = STATE_WRITE_STARTED;
- }
- InputStream in = null;
- OutputStream out = null;
- ParcelFileDescriptor source = null;
- ParcelFileDescriptor sink = null;
- synchronized (mLock) {
- mWriteThread = Thread.currentThread();
- }
- try {
- ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
- source = pipe[0];
- sink = pipe[1];
-
- in = new FileInputStream(source.getFileDescriptor());
- out = new FileOutputStream(mFile);
-
- // Async call to initiate the other process writing the data.
- mRemoteInterface.write(mPages, sink, mIWriteResultCallback);
-
- // Close the source. It is now held by the client.
- sink.close();
- sink = null;
-
- final byte[] buffer = new byte[8192];
- while (true) {
- if (Thread.currentThread().isInterrupted()) {
- Thread.currentThread().interrupt();
- break;
- }
- final int readByteCount = in.read(buffer);
- if (readByteCount < 0) {
- break;
- }
- out.write(buffer, 0, readByteCount);
- }
- synchronized (mLock) {
- while (true) {
- if (mState == STATE_WRITE_COMPLETED) {
- break;
- }
- try {
- mLock.wait();
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error writing print document", re);
- mState = STATE_FAILED;
- mTaskQueue.remove(this);
- notifyWriteFailedQuietly();
- } catch (IOException ioe) {
- Slog.e(LOG_TAG, "Error writing print document", ioe);
- mState = STATE_FAILED;
- mTaskQueue.remove(this);
- notifyWriteFailedQuietly();
- } finally {
- IoUtils.closeQuietly(in);
- IoUtils.closeQuietly(out);
- IoUtils.closeQuietly(sink);
- IoUtils.closeQuietly(source);
- }
- return null;
- }
-
- private void cancelSignalQuietlyLocked() {
- if (mCancellationSignal != null) {
- try {
- mCancellationSignal.cancel();
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error cancelling layout", re);
- notifyWriteFailedQuietly();
- }
- }
- }
-
- private void notifyWriteFailedQuietly() {
- try {
- mIWriteResultCallback.onWriteFailed(null);
- } catch (RemoteException re) {
- /* ignore */
- }
+ public void finish() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "finish()");
}
-
- private void throwIfCancelledLocked() {
- if (isCancelled()) {
- throw new IllegalStateException("Already cancelled");
- }
+ try {
+ mRemoteInterface.finish();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling finish()", re);
}
}
}
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index 39c0bddf2dcb..dc8fab6135b2 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -521,6 +521,14 @@ class AlarmManagerService extends IAlarmManager.Stub {
throw new IllegalArgumentException("Invalid alarm type " + type);
}
+ if (triggerAtTime < 0) {
+ final long who = Binder.getCallingUid();
+ final long what = Binder.getCallingPid();
+ Slog.w(TAG, "Invalid alarm trigger time! " + triggerAtTime + " from uid=" + who
+ + " pid=" + what);
+ triggerAtTime = 0;
+ }
+
final long nowElapsed = SystemClock.elapsedRealtime();
final long triggerElapsed = convertToElapsed(triggerAtTime, type);
final long maxElapsed;
@@ -567,7 +575,12 @@ class AlarmManagerService extends IAlarmManager.Stub {
}
if (DEBUG_VALIDATE) {
- if (doValidate && validateConsistencyLocked()) {
+ if (doValidate && !validateConsistencyLocked()) {
+ Slog.v(TAG, "Tipping-point operation: type=" + type + " when=" + when
+ + " when(hex)=" + Long.toHexString(when)
+ + " whenElapsed=" + whenElapsed + " maxWhen=" + maxWhen
+ + " interval=" + interval + " op=" + operation
+ + " standalone=" + isStandalone);
rebatchAllAlarmsLocked(false);
reschedule = true;
}
@@ -586,8 +599,9 @@ class AlarmManagerService extends IAlarmManager.Stub {
final int NZ = mAlarmBatches.size();
for (int iz = 0; iz < NZ; iz++) {
Batch bz = mAlarmBatches.get(iz);
- Slog.v(TAG, "Batch " + iz + ": " + bz);
+ pw.append("Batch "); pw.print(iz); pw.append(": "); pw.println(bz);
dumpAlarmList(pw, bz.alarms, " ", nowELAPSED, nowRTC);
+ pw.flush();
Slog.v(TAG, bs.toString());
bs.reset();
}
@@ -626,15 +640,17 @@ class AlarmManagerService extends IAlarmManager.Stub {
private void rescheduleKernelAlarmsLocked() {
// Schedule the next upcoming wakeup alarm. If there is a deliverable batch
// prior to that which contains no wakeups, we schedule that as well.
- final Batch firstWakeup = findFirstWakeupBatchLocked();
- final Batch firstBatch = mAlarmBatches.get(0);
- if (firstWakeup != null && mNextWakeup != firstWakeup.start) {
- mNextWakeup = firstWakeup.start;
- setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start);
- }
- if (firstBatch != firstWakeup && mNextNonWakeup != firstBatch.start) {
- mNextNonWakeup = firstBatch.start;
- setLocked(ELAPSED_REALTIME, firstBatch.start);
+ if (mAlarmBatches.size() > 0) {
+ final Batch firstWakeup = findFirstWakeupBatchLocked();
+ final Batch firstBatch = mAlarmBatches.get(0);
+ if (firstWakeup != null && mNextWakeup != firstWakeup.start) {
+ mNextWakeup = firstWakeup.start;
+ setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start);
+ }
+ if (firstBatch != firstWakeup && mNextNonWakeup != firstBatch.start) {
+ mNextNonWakeup = firstBatch.start;
+ setLocked(ELAPSED_REALTIME, firstBatch.start);
+ }
}
}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 9615ff5fb89d..17ae85f39afd 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -2433,7 +2433,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
CompareResult<RouteInfo> routeDiff = new CompareResult<RouteInfo>();
if (curLp != null) {
// check for the delta between the current set and the new
- routeDiff = curLp.compareRoutes(newLp);
+ routeDiff = curLp.compareAllRoutes(newLp);
dnsDiff = curLp.compareDnses(newLp);
} else if (newLp != null) {
routeDiff.added = newLp.getAllRoutes();
@@ -3909,8 +3909,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
NetworkInfo.State state = mCs
.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
if (state != NetworkInfo.State.CONNECTED) {
- log("isMobileOk: not connected ni=" +
+ if (VDBG) {
+ log("isMobileOk: not connected ni=" +
mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
+ }
sleep(1);
result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION;
continue;
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 35656f8367c2..da584e2012b4 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -899,7 +899,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
+ "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
+ " calling userId = " + userId + ", foreground user id = "
- + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid());
+ + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()
+ + InputMethodUtils.getApiCallStack());
}
if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) {
return true;
@@ -919,7 +920,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
return true;
}
- Slog.w(TAG, "--- IPC called from background users. Ignore. \n" + getStackTrace());
+ Slog.w(TAG, "--- IPC called from background users. Ignore. \n"
+ + InputMethodUtils.getStackTrace());
return false;
}
@@ -2473,7 +2475,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
HashMap<String, InputMethodInfo> map, boolean resetDefaultEnabledIme) {
if (DEBUG) {
Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
- + " \n ------ \n" + getStackTrace());
+ + " \n ------ \n" + InputMethodUtils.getStackTrace());
}
list.clear();
map.clear();
@@ -3468,22 +3470,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
- // ----------------------------------------------------------------------
- // Utilities for debug
- private static String getStackTrace() {
- final StringBuilder sb = new StringBuilder();
- try {
- throw new RuntimeException();
- } catch (RuntimeException e) {
- final StackTraceElement[] frames = e.getStackTrace();
- // Start at 1 because the first frame is here and we don't care about it
- for (int j = 1; j < frames.length; ++j) {
- sb.append(frames[j].toString() + "\n");
- }
- }
- return sb.toString();
- }
-
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index bdf6129f84be..1e8a589992d1 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -1633,7 +1633,7 @@ public class NotificationManagerService extends INotificationManager.Stub
Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification);
}
checkCallerIsSystemOrSameApp(pkg);
- final boolean isSystemNotification = isCallerSystem() || ("android".equals(pkg));
+ final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));
final int userId = ActivityManager.handleIncomingUser(callingPid,
callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
@@ -1873,9 +1873,9 @@ public class NotificationManagerService extends INotificationManager.Stub
}
mSoundNotification = r;
// do not play notifications if stream volume is 0 (typically because
- // ringer mode is silent) or if speech recognition is active.
+ // ringer mode is silent) or if there is a user of exclusive audio focus
if ((audioManager.getStreamVolume(audioStreamType) != 0)
- && !audioManager.isSpeechRecognitionActive()) {
+ && !audioManager.isAudioFocusExclusive()) {
final long identity = Binder.clearCallingIdentity();
try {
final IRingtonePlayer player = mAudioService.getRingtonePlayer();
@@ -2151,14 +2151,18 @@ public class NotificationManagerService extends INotificationManager.Stub
cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId);
}
- // Return true if the caller is a system or phone UID and therefore should not have
+ // Return true if the UID is a system or phone UID and therefore should not have
// any notifications or toasts blocked.
- boolean isCallerSystem() {
- final int uid = Binder.getCallingUid();
+ boolean isUidSystem(int uid) {
final int appid = UserHandle.getAppId(uid);
return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
}
+ // same as isUidSystem(int, int) for the Binder caller's UID.
+ boolean isCallerSystem() {
+ return isUidSystem(Binder.getCallingUid());
+ }
+
void checkCallerIsSystem() {
if (isCallerSystem()) {
return;
diff --git a/services/java/com/android/server/print/RemotePrintService.java b/services/java/com/android/server/print/RemotePrintService.java
index 203bc866bb21..7acf6ab11576 100644
--- a/services/java/com/android/server/print/RemotePrintService.java
+++ b/services/java/com/android/server/print/RemotePrintService.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -28,6 +29,7 @@ import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.IBinder.DeathRecipient;
import android.print.IPrinterDiscoveryObserver;
import android.print.PrintJobInfo;
import android.print.PrintManager;
@@ -46,11 +48,11 @@ import java.util.List;
* and unbinding from the remote implementation. Clients can call methods of
* this class without worrying about when and how to bind/unbind.
*/
-final class RemotePrintService {
+final class RemotePrintService implements DeathRecipient {
private static final String LOG_TAG = "RemotePrintService";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
private final Context mContext;
@@ -98,7 +100,16 @@ final class RemotePrintService {
}
public void onAllPrintJobsHandled() {
- mHandler.sendEmptyMessage(MyHandler.MSG_ALL_PRINT_JOBS_HANDLED);
+ mHandler.sendEmptyMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED);
+ }
+
+ @Override
+ public void binderDied() {
+ mHandler.sendEmptyMessage(MyHandler.MSG_BINDER_DIED);
+ }
+
+ private void handleBinderDied() {
+ ensureUnbound();
}
private void handleOnAllPrintJobsHandled() {
@@ -113,7 +124,7 @@ final class RemotePrintService {
}
public void onRequestCancelPrintJob(PrintJobInfo printJob) {
- mHandler.obtainMessage(MyHandler.MSG_REQUEST_CANCEL_PRINT_JOB,
+ mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_CANCEL_PRINT_JOB,
printJob).sendToTarget();
}
@@ -126,7 +137,7 @@ final class RemotePrintService {
Slog.i(LOG_TAG, "[user: " + mUserId + "] handleOnRequestCancelPrintJob()");
}
try {
- mPrintService.requestCancelPrintJob(printJob);
+ mPrintService.onRequestCancelPrintJob(printJob);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error canceling pring job.", re);
}
@@ -134,7 +145,7 @@ final class RemotePrintService {
}
public void onPrintJobQueued(PrintJobInfo printJob) {
- mHandler.obtainMessage(MyHandler.MSG_PRINT_JOB_QUEUED,
+ mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED,
printJob).sendToTarget();
}
@@ -161,7 +172,7 @@ final class RemotePrintService {
}
public void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer) {
- mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_DISCOVERY, observer).sendToTarget();
+ mHandler.obtainMessage(MyHandler.MSG_ON_START_PRINTER_DISCOVERY, observer).sendToTarget();
}
private void handleOnStartPrinterDiscovery(final IPrinterDiscoveryObserver observer) {
@@ -179,7 +190,7 @@ final class RemotePrintService {
Slog.i(LOG_TAG, "[user: " + mUserId + "] onStartPrinterDiscovery()");
}
try {
- mPrintService.startPrinterDiscovery(observer);
+ mPrintService.onStartPrinterDiscovery(observer);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error announcing start printer dicovery.", re);
}
@@ -187,7 +198,7 @@ final class RemotePrintService {
}
public void onStopPrinterDiscovery() {
- mHandler.sendEmptyMessage(MyHandler.MSG_STOP_PRINTER_DISCOVERY);
+ mHandler.sendEmptyMessage(MyHandler.MSG_ON_STOP_PRINTER_DISCOVERY);
}
private void handleStopPrinterDiscovery() {
@@ -205,13 +216,40 @@ final class RemotePrintService {
Slog.i(LOG_TAG, "[user: " + mUserId + "] onStopPrinterDiscovery()");
}
try {
- mPrintService.stopPrinterDiscovery();
+ mPrintService.onStopPrinterDiscovery();
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error announcing stop printer dicovery.", re);
}
}
}
+ public void onRequestUpdatePrinters(List<PrinterId> printerIds) {
+ mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_UPDATE_PRINTERS,
+ printerIds).sendToTarget();
+ }
+
+ private void handleReqeustUpdatePritners(final List<PrinterId> printerIds) {
+ throwIfDestroyed();
+ if (!isBound()) {
+ ensureBound();
+ mPendingCommands.add(new Runnable() {
+ @Override
+ public void run() {
+ handleReqeustUpdatePritners(printerIds);
+ }
+ });
+ } else {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserId + "] handleReqeustUpdatePritners()");
+ }
+ try {
+ mPrintService.onRequestUpdatePrinters(printerIds);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error requesting to update printers.", re);
+ }
+ }
+ }
+
private boolean isBound() {
return mPrintService != null;
}
@@ -243,6 +281,7 @@ final class RemotePrintService {
} catch (RemoteException re) {
/* ignore */
}
+ mPrintService.asBinder().unlinkToDeath(this, 0);
mPrintService = null;
mContext.unbindService(mServiceConnection);
}
@@ -263,10 +302,16 @@ final class RemotePrintService {
mBinding = false;
mPrintService = IPrintService.Stub.asInterface(service);
try {
+ service.linkToDeath(RemotePrintService.this, 0);
+ } catch (RemoteException re) {
+ handleBinderDied();
+ return;
+ }
+ try {
mPrintService.setClient(mPrintServiceClient);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error setting client for: " + service, re);
- handleDestroy();
+ handleBinderDied();
return;
}
final int pendingCommandCount = mPendingCommands.size();
@@ -283,47 +328,59 @@ final class RemotePrintService {
}
private final class MyHandler extends Handler {
- public static final int MSG_ALL_PRINT_JOBS_HANDLED = 1;
- public static final int MSG_REQUEST_CANCEL_PRINT_JOB = 2;
- public static final int MSG_PRINT_JOB_QUEUED = 3;
- public static final int MSG_START_PRINTER_DISCOVERY = 4;
- public static final int MSG_STOP_PRINTER_DISCOVERY = 5;
- public static final int MSG_DESTROY = 6;
+ public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 1;
+ public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 2;
+ public static final int MSG_ON_PRINT_JOB_QUEUED = 3;
+ public static final int MSG_ON_START_PRINTER_DISCOVERY = 4;
+ public static final int MSG_ON_STOP_PRINTER_DISCOVERY = 5;
+ public static final int MSG_ON_REQUEST_UPDATE_PRINTERS = 6;
+ public static final int MSG_DESTROY = 7;
+ public static final int MSG_BINDER_DIED = 8;
public MyHandler(Looper looper) {
super(looper, null, false);
}
@Override
+ @SuppressWarnings("unchecked")
public void handleMessage(Message message) {
switch (message.what) {
- case MSG_ALL_PRINT_JOBS_HANDLED: {
+ case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
handleOnAllPrintJobsHandled();
} break;
- case MSG_REQUEST_CANCEL_PRINT_JOB: {
+ case MSG_ON_REQUEST_CANCEL_PRINT_JOB: {
PrintJobInfo printJob = (PrintJobInfo) message.obj;
handleOnRequestCancelPrintJob(printJob);
} break;
- case MSG_PRINT_JOB_QUEUED: {
+ case MSG_ON_PRINT_JOB_QUEUED: {
PrintJobInfo printJob = (PrintJobInfo) message.obj;
handleOnPrintJobQueued(printJob);
} break;
- case MSG_START_PRINTER_DISCOVERY: {
+ case MSG_ON_START_PRINTER_DISCOVERY: {
IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) message.obj;
handleOnStartPrinterDiscovery(new SecurePrinterDiscoveryObserver(
mComponentName, observer));
} break;
- case MSG_STOP_PRINTER_DISCOVERY: {
+ case MSG_ON_REQUEST_UPDATE_PRINTERS: {
+ List<PrinterId> printerIds = (List<PrinterId>) message.obj;
+ handleReqeustUpdatePritners(printerIds);
+ } break;
+
+ case MSG_ON_STOP_PRINTER_DISCOVERY: {
handleStopPrinterDiscovery();
} break;
case MSG_DESTROY: {
handleDestroy();
} break;
+
+ case MSG_BINDER_DIED: {
+ handleBinderDied();
+ } break;
}
}
}
@@ -420,22 +477,32 @@ final class RemotePrintService {
}
@Override
- public void addDiscoveredPrinters(List<PrinterInfo> printers) {
+ public void onPrintersAdded(List<PrinterInfo> printers) {
+ throwIfPrinterIdsForPrinterInfoTampered(printers);
+ try {
+ mDecoratedObsever.onPrintersAdded(printers);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error delegating to onPrintersAdded", re);
+ }
+ }
+
+ @Override
+ public void onPrintersUpdated(List<PrinterInfo> printers) {
throwIfPrinterIdsForPrinterInfoTampered(printers);
try {
- mDecoratedObsever.addDiscoveredPrinters(printers);
+ mDecoratedObsever.onPrintersUpdated(printers);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error delegating to addDiscoveredPrinters", re);
+ Slog.e(LOG_TAG, "Error delegating to onPrintersUpdated.", re);
}
}
@Override
- public void removeDiscoveredPrinters(List<PrinterId> printerIds) {
+ public void onPrintersRemoved(List<PrinterId> printerIds) {
throwIfPrinterIdsTampered(printerIds);
try {
- mDecoratedObsever.removeDiscoveredPrinters(printerIds);
+ mDecoratedObsever.onPrintersRemoved(printerIds);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error delegating to removeDiscoveredPrinters", re);
+ Slog.e(LOG_TAG, "Error delegating to onPrintersRemoved", re);
}
}
diff --git a/services/java/com/android/server/print/RemotePrintSpooler.java b/services/java/com/android/server/print/RemotePrintSpooler.java
index 6445c2ee9814..fe5b0675413a 100644
--- a/services/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/java/com/android/server/print/RemotePrintSpooler.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -34,6 +35,7 @@ import android.print.IPrintSpoolerClient;
import android.print.IPrinterDiscoveryObserver;
import android.print.PrintAttributes;
import android.print.PrintJobInfo;
+import android.print.PrinterId;
import android.util.Slog;
import android.util.TimedRemoteCaller;
@@ -54,7 +56,7 @@ final class RemotePrintSpooler {
private static final String LOG_TAG = "RemotePrintSpooler";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
private static final long BIND_SPOOLER_SERVICE_TIMEOUT = 10000;
@@ -88,11 +90,14 @@ final class RemotePrintSpooler {
private boolean mDestroyed;
+ private boolean mCanUnbind;
+
public static interface PrintSpoolerCallbacks {
public void onPrintJobQueued(PrintJobInfo printJob);
public void onStartPrinterDiscovery(IPrinterDiscoveryObserver observer);
public void onStopPrinterDiscovery();
public void onAllPrintJobsForServiceHandled(ComponentName printService);
+ public void onRequestUpdatePrinters(List<PrinterId> printerIds);
}
public RemotePrintSpooler(Context context, int userId,
@@ -111,6 +116,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
+ mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfos()");
@@ -122,6 +128,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error getting print jobs.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error getting print jobs.", te);
+ } finally {
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
}
return null;
}
@@ -131,6 +142,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
+ mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] createPrintJob()");
@@ -142,6 +154,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error creating print job.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error creating print job.", te);
+ } finally {
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
}
return null;
}
@@ -150,6 +167,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
+ mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] cancelPrintJob()");
@@ -161,6 +179,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error canceling print job.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error canceling print job.", te);
+ } finally {
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
}
return false;
}
@@ -169,6 +192,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
+ mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] writePrintJobData()");
@@ -183,6 +207,10 @@ final class RemotePrintSpooler {
// We passed the file descriptor across and now the other
// side is responsible to close it, so close the local copy.
IoUtils.closeQuietly(fd);
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
}
}
@@ -190,6 +218,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
+ mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfo()");
@@ -201,6 +230,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error getting print job info.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error getting print job info.", te);
+ } finally {
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
}
return null;
}
@@ -209,6 +243,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
+ mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobState()");
@@ -220,6 +255,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error setting print job state.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error setting print job state.", te);
+ } finally {
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
}
return false;
}
@@ -228,6 +268,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
+ mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobTag()");
@@ -239,6 +280,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error setting print job tag.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error setting print job tag.", te);
+ } finally {
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
}
return false;
}
@@ -247,6 +293,7 @@ final class RemotePrintSpooler {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
+ mCanUnbind = false;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
@@ -258,6 +305,11 @@ final class RemotePrintSpooler {
Slog.e(LOG_TAG, "Error asking for active print job notification.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error asking for active print job notification.", te);
+ } finally {
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
}
}
@@ -270,6 +322,7 @@ final class RemotePrintSpooler {
throwIfDestroyedLocked();
unbindLocked();
mDestroyed = true;
+ mCanUnbind = false;
}
}
@@ -314,15 +367,29 @@ final class RemotePrintSpooler {
/* ignore */
}
}
+
+ mCanUnbind = true;
+ mLock.notifyAll();
}
private void unbindLocked() {
- if (DEBUG) {
- Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] unbindLocked()");
+ while (true) {
+ if (mCanUnbind) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] unbindLocked()");
+ }
+ clearClientLocked();
+ mRemoteInstance = null;
+ mContext.unbindService(mServiceConnection);
+ return;
+ }
+ try {
+ mLock.wait();
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
}
- clearClientLocked();
- mRemoteInstance = null;
- mContext.unbindService(mServiceConnection);
+
}
private void setClientLocked() {
@@ -597,7 +664,7 @@ final class RemotePrintSpooler {
}
@Override
- public void onStopPrinterDiscovery() throws RemoteException {
+ public void onStopPrinterDiscovery() {
RemotePrintSpooler spooler = mWeakSpooler.get();
if (spooler != null) {
final long identity = Binder.clearCallingIdentity();
@@ -608,5 +675,18 @@ final class RemotePrintSpooler {
}
}
}
+
+ @Override
+ public void onRequestUpdatePrinters(List<PrinterId> printerIds) {
+ RemotePrintSpooler spooler = mWeakSpooler.get();
+ if (spooler != null) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ spooler.mCallbacks.onRequestUpdatePrinters(printerIds);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
}
}
diff --git a/services/java/com/android/server/print/UserState.java b/services/java/com/android/server/print/UserState.java
index 5cef4d3ceae7..c41f9b02056f 100644
--- a/services/java/com/android/server/print/UserState.java
+++ b/services/java/com/android/server/print/UserState.java
@@ -23,6 +23,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.print.IPrinterDiscoveryObserver;
import android.print.PrintJobInfo;
+import android.print.PrinterId;
import android.printservice.PrintServiceInfo;
import android.provider.Settings;
import android.text.TextUtils;
@@ -138,6 +139,21 @@ final class UserState implements PrintSpoolerCallbacks {
}
}
+ @Override
+ public void onRequestUpdatePrinters(List<PrinterId> printerIds) {
+ final RemotePrintService service;
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ if (mActiveServices.isEmpty()) {
+ return;
+ }
+ service = mActiveServices.get(printerIds.get(0).getService());
+ }
+ if (service != null) {
+ service.onRequestUpdatePrinters(printerIds);
+ }
+ }
+
public void updateIfNeededLocked() {
throwIfDestroyedLocked();
if (readConfigurationLocked()) {
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ArrayMapTests.java b/tests/ActivityTests/src/com/google/android/test/activity/ArrayMapTests.java
index 28e86bf502a0..4ad6dc7d8239 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/ArrayMapTests.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/ArrayMapTests.java
@@ -146,6 +146,65 @@ public class ArrayMapTests {
}
}
+ if (map.entrySet().hashCode() != array.entrySet().hashCode()) {
+ Log.e("test", "Entry set hash codes differ: map=0x"
+ + Integer.toHexString(map.entrySet().hashCode()) + " array=0x"
+ + Integer.toHexString(array.entrySet().hashCode()));
+ return false;
+ }
+
+ if (!map.entrySet().equals(array.entrySet())) {
+ Log.e("test", "Failed calling equals on map entry set against array set");
+ return false;
+ }
+
+ if (!array.entrySet().equals(map.entrySet())) {
+ Log.e("test", "Failed calling equals on array entry set against map set");
+ return false;
+ }
+
+ if (map.keySet().hashCode() != array.keySet().hashCode()) {
+ Log.e("test", "Key set hash codes differ: map=0x"
+ + Integer.toHexString(map.keySet().hashCode()) + " array=0x"
+ + Integer.toHexString(array.keySet().hashCode()));
+ return false;
+ }
+
+ if (!map.keySet().equals(array.keySet())) {
+ Log.e("test", "Failed calling equals on map key set against array set");
+ return false;
+ }
+
+ if (!array.keySet().equals(map.keySet())) {
+ Log.e("test", "Failed calling equals on array key set against map set");
+ return false;
+ }
+
+ if (!map.keySet().containsAll(array.keySet())) {
+ Log.e("test", "Failed map key set contains all of array key set");
+ return false;
+ }
+
+ if (!array.keySet().containsAll(map.keySet())) {
+ Log.e("test", "Failed array key set contains all of map key set");
+ return false;
+ }
+
+ if (!array.containsAll(map.keySet())) {
+ Log.e("test", "Failed array contains all of map key set");
+ return false;
+ }
+
+ if (!map.entrySet().containsAll(array.entrySet())) {
+ Log.e("test", "Failed map entry set contains all of array entry set");
+ return false;
+ }
+
+ if (!array.entrySet().containsAll(map.entrySet())) {
+ Log.e("test", "Failed array entry set contains all of map entry set");
+ return false;
+ }
+
return true;
}