am 5993e2d0: (-s ours) Merge "DO NOT MERGE. Port "respond via SMS message" feature to new Telecomm. (1/4)" into lmp-preview-dev

* commit '5993e2d0fa5807e6b58ad559ee94befd09b82431':
  DO NOT MERGE. Port "respond via SMS message" feature to new Telecomm. (1/4)
diff --git a/Android.mk b/Android.mk
index 52459ae..c889fa1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -118,6 +118,7 @@
 	core/java/android/content/IIntentReceiver.aidl \
 	core/java/android/content/IIntentSender.aidl \
 	core/java/android/content/IOnPrimaryClipChangedListener.aidl \
+	core/java/android/content/IRestrictionsManager.aidl \
 	core/java/android/content/ISyncAdapter.aidl \
 	core/java/android/content/ISyncContext.aidl \
 	core/java/android/content/ISyncServiceAdapter.aidl \
@@ -381,54 +382,14 @@
 LOCAL_STATIC_JAVA_LIBRARIES := framework-base
 LOCAL_DX_FLAGS := --core-library
 
-# Packages to include, use \* wildcard to include descendants.
+# List of packages to include along with their descendants.
 LOCAL_JAR_PACKAGES := \
-	android \
-	android.accessibilityservice\* \
-	android.accounts\* \
-	android.alsa\* \
-	android.animation\* \
-	android.annotation\* \
-	android.app\* \
-	android.appwidget\* \
-	android.bluetooth\* \
-	android.content\* \
-	android.content\* \
-	android.database\* \
-	android.ddm\* \
-	android.drm\* \
-	android.emoji\* \
-	android.filterfw\* \
-	android.filterpacks\* \
-	android.gesture\* \
-	android.graphics\* \
-	android.inputmethodservice\* \
-	android.location\* \
-	android.media\* \
-	android.mtp\* \
-	android.net\* \
-	android.nfc\* \
-	android.opengl\* \
-	android.os\* \
-	android.preference\* \
-	android.print\* \
-	android.printservice\* \
-	android.provider\* \
-	android.renderscript\* \
-	android.sax\* \
-	android.security\* \
-	android.service\* \
-	android.speech\* \
-	android.system\* \
-	android.telecomm\* \
-	android.telephony\* \
-	android.test\* \
-	android.text\* \
-	android.transition\* \
-	android.util\* \
-	android.view\* \
-	android.webkit\* \
-	android.widget\*
+    android
+
+# List of packages to exclude along with their descendants.
+# Overrides inclusion.
+LOCAL_JAR_EXCLUDE_PACKAGES := \
+    android.hardware
 
 # List of classes and interfaces which should be loaded by the Zygote.
 LOCAL_JAVA_RESOURCE_FILES += $(LOCAL_PATH)/preloaded-classes
@@ -446,16 +407,11 @@
 LOCAL_STATIC_JAVA_LIBRARIES := framework-base
 LOCAL_DX_FLAGS := --core-library
 
-# Packages to include, use \* wildcard to include descendants.
-# 'android' is required to be able to include 'android.hardware',
-# however, it causes classes in 'android' to be part of framework
-# and framework2.
-# TODO: Finer grained matching.
+# List of packages to include along with their descendants.
 LOCAL_JAR_PACKAGES := \
-	android \
-	android.hardware\* \
-	com\* \
-	javax\*
+    android.hardware \
+    com \
+    javax
 
 include $(BUILD_JAVA_LIBRARY)
 framework2_module := $(LOCAL_INSTALLED_MODULE)
diff --git a/api/current.txt b/api/current.txt
index cc57576..3962492 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -27,6 +27,7 @@
     field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
     field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
     field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
+    field public static final java.lang.String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
     field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
     field public static final java.lang.String BIND_VOICE_INTERACTION = "android.permission.BIND_VOICE_INTERACTION";
     field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE";
@@ -111,6 +112,7 @@
     field public static final java.lang.String RECEIVE_SMS = "android.permission.RECEIVE_SMS";
     field public static final java.lang.String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
     field public static final java.lang.String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
+    field public static final java.lang.String RECOVERY = "android.permission.RECOVERY";
     field public static final java.lang.String REORDER_TASKS = "android.permission.REORDER_TASKS";
     field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
     field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
@@ -129,6 +131,7 @@
     field public static final java.lang.String SET_WALLPAPER = "android.permission.SET_WALLPAPER";
     field public static final java.lang.String SET_WALLPAPER_HINTS = "android.permission.SET_WALLPAPER_HINTS";
     field public static final java.lang.String SIGNAL_PERSISTENT_PROCESSES = "android.permission.SIGNAL_PERSISTENT_PROCESSES";
+    field public static final java.lang.String SIM_COMMUNICATION = "android.permission.SIM_COMMUNICATION";
     field public static final java.lang.String STATUS_BAR = "android.permission.STATUS_BAR";
     field public static final java.lang.String SUBSCRIBED_FEEDS_READ = "android.permission.SUBSCRIBED_FEEDS_READ";
     field public static final java.lang.String SUBSCRIBED_FEEDS_WRITE = "android.permission.SUBSCRIBED_FEEDS_WRITE";
@@ -488,7 +491,7 @@
     field public static final int editTextStyle = 16842862; // 0x101006e
     field public static final deprecated int editable = 16843115; // 0x101016b
     field public static final int editorExtras = 16843300; // 0x1010224
-    field public static final int elegantTextHeight = 16843867; // 0x101045b
+    field public static final int elegantTextHeight = 16843871; // 0x101045f
     field public static final int elevation = 16843842; // 0x1010442
     field public static final int ellipsize = 16842923; // 0x10100ab
     field public static final int ems = 16843096; // 0x1010158
@@ -743,6 +746,7 @@
     field public static final int layout_centerVertical = 16843153; // 0x1010191
     field public static final int layout_column = 16843084; // 0x101014c
     field public static final int layout_columnSpan = 16843645; // 0x101037d
+    field public static final int layout_columnWeight = 16843867; // 0x101045b
     field public static final int layout_gravity = 16842931; // 0x10100b3
     field public static final int layout_height = 16842997; // 0x10100f5
     field public static final int layout_margin = 16842998; // 0x10100f6
@@ -754,6 +758,7 @@
     field public static final int layout_marginTop = 16843000; // 0x10100f8
     field public static final int layout_row = 16843643; // 0x101037b
     field public static final int layout_rowSpan = 16843644; // 0x101037c
+    field public static final int layout_rowWeight = 16843866; // 0x101045a
     field public static final int layout_scale = 16843155; // 0x1010193
     field public static final int layout_span = 16843085; // 0x101014d
     field public static final int layout_toEndOf = 16843704; // 0x10103b8
@@ -854,6 +859,7 @@
     field public static final int overScrollFooter = 16843459; // 0x10102c3
     field public static final int overScrollHeader = 16843458; // 0x10102c2
     field public static final int overScrollMode = 16843457; // 0x10102c1
+    field public static final int overlapAnchor = 16843876; // 0x1010464
     field public static final int overridesImplicitlyEnabledSubtype = 16843682; // 0x10103a2
     field public static final int packageNames = 16843649; // 0x1010381
     field public static final int padding = 16842965; // 0x10100d5
@@ -995,6 +1001,9 @@
     field public static final int scrollbars = 16842974; // 0x10100de
     field public static final int scrollingCache = 16843006; // 0x10100fe
     field public static final deprecated int searchButtonText = 16843269; // 0x1010205
+    field public static final int searchKeyphrase = 16843873; // 0x1010461
+    field public static final int searchKeyphraseId = 16843872; // 0x1010460
+    field public static final int searchKeyphraseSupportedLocales = 16843874; // 0x1010462
     field public static final int searchMode = 16843221; // 0x10101d5
     field public static final int searchSettingsDescription = 16843402; // 0x101028a
     field public static final int searchSuggestAuthority = 16843222; // 0x10101d6
@@ -1009,7 +1018,7 @@
     field public static final int selectAllOnFocus = 16843102; // 0x101015e
     field public static final int selectable = 16843238; // 0x10101e6
     field public static final int selectableItemBackground = 16843534; // 0x101030e
-    field public static final int selectableItemBackgroundBorderless = 16843866; // 0x101045a
+    field public static final int selectableItemBackgroundBorderless = 16843870; // 0x101045e
     field public static final int selectedDateVerticalBar = 16843591; // 0x1010347
     field public static final int selectedWeekBackgroundColor = 16843586; // 0x1010342
     field public static final int sessionService = 16843839; // 0x101043f
@@ -1237,6 +1246,8 @@
     field public static final int transition = 16843743; // 0x10103df
     field public static final int transitionGroup = 16843803; // 0x101041b
     field public static final int transitionOrdering = 16843744; // 0x10103e0
+    field public static final int translateX = 16843868; // 0x101045c
+    field public static final int translateY = 16843869; // 0x101045d
     field public static final int translationX = 16843554; // 0x1010322
     field public static final int translationY = 16843555; // 0x1010323
     field public static final int translationZ = 16843796; // 0x1010414
@@ -1333,6 +1344,7 @@
     field public static final int windowTitleBackgroundStyle = 16842844; // 0x101005c
     field public static final int windowTitleSize = 16842842; // 0x101005a
     field public static final int windowTitleStyle = 16842843; // 0x101005b
+    field public static final int windowTransitionBackgroundFadeDuration = 16843875; // 0x1010463
     field public static final int windowTranslucentNavigation = 16843760; // 0x10103f0
     field public static final int windowTranslucentStatus = 16843759; // 0x10103ef
     field public static final int writePermission = 16842760; // 0x1010008
@@ -5181,15 +5193,15 @@
   }
 
   public class DevicePolicyManager {
-    method public void addForwardingIntentFilter(android.content.ComponentName, android.content.IntentFilter, int);
+    method public void addCrossProfileIntentFilter(android.content.ComponentName, android.content.IntentFilter, int);
     method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName);
     method public void addUserRestriction(android.content.ComponentName, java.lang.String);
-    method public void clearForwardingIntentFilters(android.content.ComponentName);
+    method public void clearCrossProfileIntentFilters(android.content.ComponentName);
+    method public void clearDeviceOwnerApp();
     method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
     method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
+    method public android.os.UserHandle createAndInitializeUser(android.content.ComponentName, java.lang.String, java.lang.String, android.content.ComponentName, android.os.Bundle);
     method public android.os.UserHandle createUser(android.content.ComponentName, java.lang.String);
-    method public void enableSystemApp(android.content.ComponentName, java.lang.String);
-    method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
     method public java.lang.String[] getAccountTypesWithManagementDisabled();
     method public java.util.List<android.content.ComponentName> getActiveAdmins();
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
@@ -5218,6 +5230,7 @@
     method public boolean isApplicationBlocked(android.content.ComponentName, java.lang.String);
     method public boolean isDeviceOwnerApp(java.lang.String);
     method public boolean isLockTaskPermitted(android.content.ComponentName);
+    method public boolean isMasterVolumeMuted(android.content.ComponentName);
     method public boolean isProfileOwnerApp(java.lang.String);
     method public void lockNow();
     method public void removeActiveAdmin(android.content.ComponentName);
@@ -5231,6 +5244,7 @@
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
     method public void setLockTaskComponents(android.content.ComponentName[]) throws java.lang.SecurityException;
+    method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
     method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int);
     method public void setMaximumTimeToLock(android.content.ComponentName, long);
     method public void setPasswordExpirationTimeout(android.content.ComponentName, long);
@@ -5244,11 +5258,14 @@
     method public void setPasswordMinimumUpperCase(android.content.ComponentName, int);
     method public void setPasswordQuality(android.content.ComponentName, int);
     method public void setProfileEnabled(android.content.ComponentName);
+    method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo);
+    method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName);
     method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public int setStorageEncryption(android.content.ComponentName, boolean);
     method public void wipeData(int);
     field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_PROFILE = "android.app.action.ACTION_PROVISION_MANAGED_PROFILE";
+    field public static final java.lang.String ACTION_SEND_PROVISIONING_VALUES = "android.app.action.ACTION_SEND_PROVISIONING_VALUES";
     field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
     field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
     field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2
@@ -5259,8 +5276,10 @@
     field public static final java.lang.String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
     field public static final java.lang.String EXTRA_PROVISIONING_DEFAULT_MANAGED_PROFILE_NAME = "android.app.extra.defaultManagedProfileName";
     field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME = "android.app.extra.deviceAdminPackageName";
-    field public static int FLAG_TO_MANAGED_PROFILE;
-    field public static int FLAG_TO_PRIMARY_USER;
+    field public static final java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.ManagedProfileEmailAddress";
+    field public static final java.lang.String EXTRA_PROVISIONING_TOKEN = "android.app.extra.token";
+    field public static int FLAG_MANAGED_CAN_ACCESS_PARENT;
+    field public static int FLAG_PARENT_CAN_ACCESS_MANAGED;
     field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
     field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
     field public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 2; // 0x2
@@ -6997,6 +7016,7 @@
     field public static final java.lang.String DISPLAY_SERVICE = "display";
     field public static final java.lang.String DOWNLOAD_SERVICE = "download";
     field public static final java.lang.String DROPBOX_SERVICE = "dropbox";
+    field public static final java.lang.String FINGERPRINT_SERVICE = "fingerprint";
     field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
     field public static final java.lang.String INPUT_SERVICE = "input";
     field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler";
@@ -7017,6 +7037,7 @@
     field public static final java.lang.String NSD_SERVICE = "servicediscovery";
     field public static final java.lang.String POWER_SERVICE = "power";
     field public static final java.lang.String PRINT_SERVICE = "print";
+    field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions";
     field public static final java.lang.String SEARCH_SERVICE = "search";
     field public static final java.lang.String SENSOR_SERVICE = "sensor";
     field public static final java.lang.String STORAGE_SERVICE = "storage";
@@ -7769,12 +7790,14 @@
     ctor public RestrictionEntry(java.lang.String, java.lang.String);
     ctor public RestrictionEntry(java.lang.String, boolean);
     ctor public RestrictionEntry(java.lang.String, java.lang.String[]);
+    ctor public RestrictionEntry(java.lang.String, int);
     ctor public RestrictionEntry(android.os.Parcel);
     method public int describeContents();
     method public java.lang.String[] getAllSelectedStrings();
     method public java.lang.String[] getChoiceEntries();
     method public java.lang.String[] getChoiceValues();
     method public java.lang.String getDescription();
+    method public int getIntValue();
     method public java.lang.String getKey();
     method public boolean getSelectedState();
     method public java.lang.String getSelectedString();
@@ -7786,6 +7809,7 @@
     method public void setChoiceValues(java.lang.String[]);
     method public void setChoiceValues(android.content.Context, int);
     method public void setDescription(java.lang.String);
+    method public void setIntValue(int);
     method public void setSelectedState(boolean);
     method public void setSelectedString(java.lang.String);
     method public void setTitle(java.lang.String);
@@ -7794,10 +7818,36 @@
     field public static final android.os.Parcelable.Creator CREATOR;
     field public static final int TYPE_BOOLEAN = 1; // 0x1
     field public static final int TYPE_CHOICE = 2; // 0x2
+    field public static final int TYPE_INTEGER = 5; // 0x5
     field public static final int TYPE_MULTI_SELECT = 4; // 0x4
     field public static final int TYPE_NULL = 0; // 0x0
   }
 
+  public class RestrictionsManager {
+    method public android.os.Bundle getApplicationRestrictions();
+    method public java.util.List<android.content.RestrictionEntry> getManifestRestrictions(java.lang.String);
+    method public boolean hasRestrictionsProvider();
+    method public void notifyPermissionResponse(java.lang.String, android.os.Bundle);
+    method public void requestPermission(java.lang.String, android.os.Bundle);
+    field public static final java.lang.String ACTION_PERMISSION_RESPONSE_RECEIVED = "android.intent.action.PERMISSION_RESPONSE_RECEIVED";
+    field public static final java.lang.String ACTION_REQUEST_PERMISSION = "android.intent.action.REQUEST_PERMISSION";
+    field public static final java.lang.String EXTRA_PACKAGE_NAME = "package_name";
+    field public static final java.lang.String EXTRA_REQUEST_BUNDLE = "request_bundle";
+    field public static final java.lang.String EXTRA_RESPONSE_BUNDLE = "response_bundle";
+    field public static final java.lang.String EXTRA_TEMPLATE_ID = "template_id";
+    field public static final java.lang.String REQUEST_KEY_APPROVE_LABEL = "android.req_template.accept";
+    field public static final java.lang.String REQUEST_KEY_DATA = "android.req_template.data";
+    field public static final java.lang.String REQUEST_KEY_DENY_LABEL = "android.req_template.reject";
+    field public static final java.lang.String REQUEST_KEY_DEVICE_NAME = "android.req_template.device";
+    field public static final java.lang.String REQUEST_KEY_ICON = "android.req_template.icon";
+    field public static final java.lang.String REQUEST_KEY_ID = "android.req_template.req_id";
+    field public static final java.lang.String REQUEST_KEY_MESSAGE = "android.req_template.mesg";
+    field public static final java.lang.String REQUEST_KEY_REQUESTOR_NAME = "android.req_template.requestor";
+    field public static final java.lang.String REQUEST_KEY_TITLE = "android.req_template.title";
+    field public static final java.lang.String REQUEST_TEMPLATE_QUESTION = "android.req_template.type.simple";
+    field public static final java.lang.String RESPONSE_KEY_BOOLEAN = "android.req_template.response";
+  }
+
   public class SearchRecentSuggestionsProvider extends android.content.ContentProvider {
     ctor public SearchRecentSuggestionsProvider();
     method public int delete(android.net.Uri, java.lang.String, java.lang.String[]);
@@ -10075,13 +10125,13 @@
     field public android.graphics.Bitmap inBitmap;
     field public int inDensity;
     field public boolean inDither;
-    field public boolean inInputShareable;
+    field public deprecated boolean inInputShareable;
     field public boolean inJustDecodeBounds;
     field public boolean inMutable;
     field public boolean inPreferQualityOverSpeed;
     field public android.graphics.Bitmap.Config inPreferredConfig;
     field public boolean inPremultiplied;
-    field public boolean inPurgeable;
+    field public deprecated boolean inPurgeable;
     field public int inSampleSize;
     field public boolean inScaled;
     field public int inScreenDensity;
@@ -10152,8 +10202,8 @@
     method public boolean clipRect(float, float, float, float, android.graphics.Region.Op);
     method public boolean clipRect(float, float, float, float);
     method public boolean clipRect(int, int, int, int);
-    method public boolean clipRegion(android.graphics.Region, android.graphics.Region.Op);
-    method public boolean clipRegion(android.graphics.Region);
+    method public deprecated boolean clipRegion(android.graphics.Region, android.graphics.Region.Op);
+    method public deprecated boolean clipRegion(android.graphics.Region);
     method public void concat(android.graphics.Matrix);
     method public void drawARGB(int, int, int, int);
     method public void drawArc(android.graphics.RectF, float, float, boolean, android.graphics.Paint);
@@ -11530,6 +11580,16 @@
     method public void startTransition(int);
   }
 
+  public class VectorDrawable extends android.graphics.drawable.Drawable {
+    ctor public VectorDrawable();
+    method public void draw(android.graphics.Canvas);
+    method public int getOpacity();
+    method public void setAlpha(int);
+    method public void setColorFilter(android.graphics.ColorFilter);
+    method public void setPadding(android.graphics.Rect);
+    method public void setPadding(int, int, int, int);
+  }
+
 }
 
 package android.graphics.drawable.shapes {
@@ -11900,6 +11960,7 @@
   public final class Sensor {
     method public int getFifoMaxEventCount();
     method public int getFifoReservedEventCount();
+    method public int getMaxDelay();
     method public float getMaximumRange();
     method public int getMinDelay();
     method public java.lang.String getName();
@@ -11909,6 +11970,9 @@
     method public int getType();
     method public java.lang.String getVendor();
     method public int getVersion();
+    method public boolean isWakeUpSensor();
+    field public static final java.lang.String SENSOR_STRING_TYPE_NON_WAKE_UP_PROXIMITY_SENSOR = "android.sensor.non_wake_up_proximity_sensor";
+    field public static final java.lang.String SENSOR_STRING_TYPE_WAKE_UP_TILT_DETECTOR = "android.sensor.wake_up_tilt_detector";
     field public static final java.lang.String STRING_TYPE_ACCELEROMETER = "android.sensor.accelerometer";
     field public static final java.lang.String STRING_TYPE_AMBIENT_TEMPERATURE = "android.sensor.ambient_temperature";
     field public static final java.lang.String STRING_TYPE_GAME_ROTATION_VECTOR = "android.sensor.game_rotation_vector";
@@ -11930,6 +11994,24 @@
     field public static final java.lang.String STRING_TYPE_STEP_COUNTER = "android.sensor.step_counter";
     field public static final java.lang.String STRING_TYPE_STEP_DETECTOR = "android.sensor.step_detector";
     field public static final deprecated java.lang.String STRING_TYPE_TEMPERATURE = "android.sensor.temperature";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_ACCELEROMETER = "android.sensor.wake_up_accelerometer";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_AMBIENT_TEMPERATURE = "android.sensor.wake_up_ambient_temperature";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_GAME_ROTATION_VECTOR = "android.sensor.wake_up_game_rotation_vector";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR = "android.sensor.wake_up_geomagnetic_rotation_vector";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_GRAVITY = "android.sensor.wake_up_gravity";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_GYROSCOPE = "android.sensor.wake_up_gyroscope";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED = "android.sensor.wake_up_gyroscope_uncalibrated";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_HEART_RATE = "android.sensor.wake_up_heart_rate";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_LIGHT = "android.sensor.wake_up_light";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_LINEAR_ACCELERATION = "android.sensor.wake_up_linear_acceleration";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_MAGNETIC_FIELD = "android.sensor.wake_up_magnetic_field";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED = "android.sensor.wake_up_magnetic_field_uncalibrated";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_ORIENTATION = "android.sensor.wake_up_orientation";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_PRESSURE = "android.sensor.wake_up_pressure";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_RELATIVE_HUMIDITY = "android.sensor.wake_up_relative_humidity";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_ROTATION_VECTOR = "android.sensor.wake_up_rotation_vector";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_STEP_COUNTER = "android.sensor.wake_up_step_counter";
+    field public static final java.lang.String STRING_TYPE_WAKE_UP_STEP_DETECTOR = "android.sensor.wake_up_step_detector";
     field public static final int TYPE_ACCELEROMETER = 1; // 0x1
     field public static final int TYPE_ALL = -1; // 0xffffffff
     field public static final int TYPE_AMBIENT_TEMPERATURE = 13; // 0xd
@@ -11943,6 +12025,7 @@
     field public static final int TYPE_LINEAR_ACCELERATION = 10; // 0xa
     field public static final int TYPE_MAGNETIC_FIELD = 2; // 0x2
     field public static final int TYPE_MAGNETIC_FIELD_UNCALIBRATED = 14; // 0xe
+    field public static final int TYPE_NON_WAKE_UP_PROXIMITY_SENSOR = 22; // 0x16
     field public static final deprecated int TYPE_ORIENTATION = 3; // 0x3
     field public static final int TYPE_PRESSURE = 6; // 0x6
     field public static final int TYPE_PROXIMITY = 8; // 0x8
@@ -11952,6 +12035,25 @@
     field public static final int TYPE_STEP_COUNTER = 19; // 0x13
     field public static final int TYPE_STEP_DETECTOR = 18; // 0x12
     field public static final deprecated int TYPE_TEMPERATURE = 7; // 0x7
+    field public static final int TYPE_WAKE_UP_ACCELEROMETER = 23; // 0x17
+    field public static final int TYPE_WAKE_UP_AMBIENT_TEMPERATURE = 33; // 0x21
+    field public static final int TYPE_WAKE_UP_GAME_ROTATION_VECTOR = 35; // 0x23
+    field public static final int TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR = 39; // 0x27
+    field public static final int TYPE_WAKE_UP_GRAVITY = 29; // 0x1d
+    field public static final int TYPE_WAKE_UP_GYROSCOPE = 26; // 0x1a
+    field public static final int TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED = 36; // 0x24
+    field public static final int TYPE_WAKE_UP_HEART_RATE = 40; // 0x28
+    field public static final int TYPE_WAKE_UP_LIGHT = 27; // 0x1b
+    field public static final int TYPE_WAKE_UP_LINEAR_ACCELERATION = 30; // 0x1e
+    field public static final int TYPE_WAKE_UP_MAGNETIC_FIELD = 24; // 0x18
+    field public static final int TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED = 34; // 0x22
+    field public static final int TYPE_WAKE_UP_ORIENTATION = 25; // 0x19
+    field public static final int TYPE_WAKE_UP_PRESSURE = 28; // 0x1c
+    field public static final int TYPE_WAKE_UP_RELATIVE_HUMIDITY = 32; // 0x20
+    field public static final int TYPE_WAKE_UP_ROTATION_VECTOR = 31; // 0x1f
+    field public static final int TYPE_WAKE_UP_STEP_COUNTER = 38; // 0x26
+    field public static final int TYPE_WAKE_UP_STEP_DETECTOR = 37; // 0x25
+    field public static final int TYPE_WAKE_UP_TILT_DETECTOR = 41; // 0x29
   }
 
   public class SensorEvent {
@@ -12053,6 +12155,7 @@
     field public static final int SENSOR_STATUS_ACCURACY_HIGH = 3; // 0x3
     field public static final int SENSOR_STATUS_ACCURACY_LOW = 1; // 0x1
     field public static final int SENSOR_STATUS_ACCURACY_MEDIUM = 2; // 0x2
+    field public static final int SENSOR_STATUS_NO_CONTACT = -1; // 0xffffffff
     field public static final int SENSOR_STATUS_UNRELIABLE = 0; // 0x0
     field public static final deprecated int SENSOR_TEMPERATURE = 4; // 0x4
     field public static final deprecated int SENSOR_TRICORDER = 64; // 0x40
@@ -13496,8 +13599,46 @@
     method public void stop();
   }
 
+  public final class AudioAttributes implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getContentType();
+    method public int getFlags();
+    method public int getUsage();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CONTENT_TYPE_MOVIE = 3; // 0x3
+    field public static final int CONTENT_TYPE_MUSIC = 2; // 0x2
+    field public static final int CONTENT_TYPE_SONIFICATION = 4; // 0x4
+    field public static final int CONTENT_TYPE_SPEECH = 1; // 0x1
+    field public static final int CONTENT_TYPE_UNKNOWN = 0; // 0x0
+    field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
+    field public static final int USAGE_ALARM = 4; // 0x4
+    field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
+    field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
+    field public static final int USAGE_ASSISTANCE_SONIFICATION = 13; // 0xd
+    field public static final int USAGE_GAME = 14; // 0xe
+    field public static final int USAGE_MEDIA = 1; // 0x1
+    field public static final int USAGE_NOTIFICATION = 5; // 0x5
+    field public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9; // 0x9
+    field public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8; // 0x8
+    field public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7; // 0x7
+    field public static final int USAGE_NOTIFICATION_EVENT = 10; // 0xa
+    field public static final int USAGE_NOTIFICATION_TELEPHONY_RINGTONE = 6; // 0x6
+    field public static final int USAGE_UNKNOWN = 0; // 0x0
+    field public static final int USAGE_VOICE_COMMUNICATION = 2; // 0x2
+    field public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = 3; // 0x3
+  }
+
+  public static class AudioAttributes.Builder {
+    ctor public AudioAttributes.Builder();
+    ctor public AudioAttributes.Builder(android.media.AudioAttributes);
+    method public android.media.AudioAttributes build();
+    method public android.media.AudioAttributes.Builder setContentType(int);
+    method public android.media.AudioAttributes.Builder setFlags(int);
+    method public android.media.AudioAttributes.Builder setLegacyStreamType(int);
+    method public android.media.AudioAttributes.Builder setUsage(int);
+  }
+
   public class AudioFormat {
-    ctor public AudioFormat();
     field public static final deprecated int CHANNEL_CONFIGURATION_DEFAULT = 1; // 0x1
     field public static final deprecated int CHANNEL_CONFIGURATION_INVALID = 0; // 0x0
     field public static final deprecated int CHANNEL_CONFIGURATION_MONO = 2; // 0x2
@@ -13548,6 +13689,7 @@
     method public void adjustStreamVolume(int, int, int);
     method public void adjustSuggestedStreamVolume(int, int, int);
     method public void adjustVolume(int, int);
+    method public int allocateAudioSessionId();
     method public void dispatchMediaKeyEvent(android.view.KeyEvent);
     method public int getMode();
     method public java.lang.String getParameters(java.lang.String);
@@ -13608,6 +13750,7 @@
     field public static final int AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK = -3; // 0xfffffffd
     field public static final int AUDIOFOCUS_REQUEST_FAILED = 0; // 0x0
     field public static final int AUDIOFOCUS_REQUEST_GRANTED = 1; // 0x1
+    field public static final int ERROR = -1; // 0xffffffff
     field public static final java.lang.String EXTRA_RINGER_MODE = "android.media.EXTRA_RINGER_MODE";
     field public static final java.lang.String EXTRA_SCO_AUDIO_PREVIOUS_STATE = "android.media.extra.SCO_AUDIO_PREVIOUS_STATE";
     field public static final java.lang.String EXTRA_SCO_AUDIO_STATE = "android.media.extra.SCO_AUDIO_STATE";
@@ -13787,6 +13930,7 @@
     method public static boolean hasProfile(int);
     method public static boolean hasProfile(int, int);
     field public static final int QUALITY_1080P = 6; // 0x6
+    field public static final int QUALITY_2160P = 8; // 0x8
     field public static final int QUALITY_480P = 4; // 0x4
     field public static final int QUALITY_720P = 5; // 0x5
     field public static final int QUALITY_CIF = 3; // 0x3
@@ -13795,6 +13939,7 @@
     field public static final int QUALITY_QCIF = 2; // 0x2
     field public static final int QUALITY_QVGA = 7; // 0x7
     field public static final int QUALITY_TIME_LAPSE_1080P = 1006; // 0x3ee
+    field public static final int QUALITY_TIME_LAPSE_2160P = 1008; // 0x3f0
     field public static final int QUALITY_TIME_LAPSE_480P = 1004; // 0x3ec
     field public static final int QUALITY_TIME_LAPSE_720P = 1005; // 0x3ed
     field public static final int QUALITY_TIME_LAPSE_CIF = 1003; // 0x3eb
@@ -15685,8 +15830,11 @@
     field public static final int TYPE_ISDB_S = 262656; // 0x40200
     field public static final int TYPE_ISDB_T = 262144; // 0x40000
     field public static final int TYPE_ISDB_TB = 262400; // 0x40100
+    field public static final int TYPE_NTSC = 1; // 0x1
     field public static final int TYPE_OTHER = 0; // 0x0
+    field public static final int TYPE_PAL = 2; // 0x2
     field public static final int TYPE_PASSTHROUGH = 65536; // 0x10000
+    field public static final int TYPE_SECAM = 3; // 0x3
     field public static final int TYPE_S_DMB = 393472; // 0x60100
     field public static final int TYPE_T_DMB = 393216; // 0x60000
   }
@@ -15737,6 +15885,7 @@
     method public android.content.Intent getIntentForSetupActivity();
     method public android.content.pm.ServiceInfo getServiceInfo();
     method public int getType();
+    method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
     method public java.lang.CharSequence loadLabel(android.content.pm.PackageManager);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final java.lang.String EXTRA_SERVICE_NAME = "serviceName";
@@ -16986,11 +17135,9 @@
   }
 
   public static final class WifiEnterpriseConfig.Eap {
-    field public static final int AKA = 5; // 0x5
     field public static final int NONE = -1; // 0xffffffff
     field public static final int PEAP = 0; // 0x0
     field public static final int PWD = 3; // 0x3
-    field public static final int SIM = 4; // 0x4
     field public static final int TLS = 1; // 0x1
     field public static final int TTLS = 2; // 0x2
   }
@@ -17024,6 +17171,7 @@
   public class WifiManager {
     method public int addNetwork(android.net.wifi.WifiConfiguration);
     method public static int calculateSignalLevel(int, int);
+    method public void cancelWps(android.net.wifi.WifiManager.ActionListener);
     method public static int compareSignalLevel(int, int);
     method public android.net.wifi.WifiManager.MulticastLock createMulticastLock(java.lang.String);
     method public android.net.wifi.WifiManager.WifiLock createWifiLock(int, java.lang.String);
@@ -17047,9 +17195,12 @@
     method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean);
     method public boolean setWifiEnabled(boolean);
     method public boolean startScan();
+    method public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsListener);
     method public int updateNetwork(android.net.wifi.WifiConfiguration);
     field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
     field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
+    field public static final int BUSY = 2; // 0x2
+    field public static final int ERROR = 0; // 0x0
     field public static final int ERROR_AUTHENTICATING = 1; // 0x1
     field public static final java.lang.String EXTRA_BSSID = "bssid";
     field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo";
@@ -17060,6 +17211,8 @@
     field public static final java.lang.String EXTRA_SUPPLICANT_ERROR = "supplicantError";
     field public static final java.lang.String EXTRA_WIFI_INFO = "wifiInfo";
     field public static final java.lang.String EXTRA_WIFI_STATE = "wifi_state";
+    field public static final int INVALID_ARGS = 8; // 0x8
+    field public static final int IN_PROGRESS = 1; // 0x1
     field public static final java.lang.String NETWORK_IDS_CHANGED_ACTION = "android.net.wifi.NETWORK_IDS_CHANGED";
     field public static final java.lang.String NETWORK_STATE_CHANGED_ACTION = "android.net.wifi.STATE_CHANGE";
     field public static final java.lang.String RSSI_CHANGED_ACTION = "android.net.wifi.RSSI_CHANGED";
@@ -17075,6 +17228,16 @@
     field public static final int WIFI_STATE_ENABLED = 3; // 0x3
     field public static final int WIFI_STATE_ENABLING = 2; // 0x2
     field public static final int WIFI_STATE_UNKNOWN = 4; // 0x4
+    field public static final int WPS_AUTH_FAILURE = 6; // 0x6
+    field public static final int WPS_OVERLAP_ERROR = 3; // 0x3
+    field public static final int WPS_TIMED_OUT = 7; // 0x7
+    field public static final int WPS_TKIP_ONLY_PROHIBITED = 5; // 0x5
+    field public static final int WPS_WEP_PROHIBITED = 4; // 0x4
+  }
+
+  public static abstract interface WifiManager.ActionListener {
+    method public abstract void onFailure(int);
+    method public abstract void onSuccess();
   }
 
   public class WifiManager.MulticastLock {
@@ -17092,11 +17255,18 @@
     method public void setWorkSource(android.os.WorkSource);
   }
 
+  public static abstract interface WifiManager.WpsListener {
+    method public abstract void onCompletion();
+    method public abstract void onFailure(int);
+    method public abstract void onStartSuccess(java.lang.String);
+  }
+
   public class WpsInfo implements android.os.Parcelable {
     ctor public WpsInfo();
     ctor public WpsInfo(android.net.wifi.WpsInfo);
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
+    field public java.lang.String BSSID;
     field public static final android.os.Parcelable.Creator CREATOR;
     field public static final int DISPLAY = 1; // 0x1
     field public static final int INVALID = 4; // 0x4
@@ -21335,6 +21505,7 @@
     method public java.util.List<android.os.UserHandle> getUserProfiles();
     method public android.os.Bundle getUserRestrictions();
     method public android.os.Bundle getUserRestrictions(android.os.UserHandle);
+    method public boolean hasUserRestriction(java.lang.String);
     method public boolean isUserAGoat();
     method public boolean isUserRunning(android.os.UserHandle);
     method public boolean isUserRunningOrStopping(android.os.UserHandle);
@@ -21371,7 +21542,9 @@
     method public abstract void cancel();
     method public abstract boolean hasVibrator();
     method public void vibrate(long);
+    method public void vibrate(long, int);
     method public void vibrate(long[], int);
+    method public void vibrate(long[], int, int);
   }
 
   public class WorkSource implements android.os.Parcelable {
@@ -22138,6 +22311,7 @@
     method protected void onDisconnected();
     method protected abstract void onPrintJobQueued(android.printservice.PrintJob);
     method protected abstract void onRequestCancelPrintJob(android.printservice.PrintJob);
+    field public static final java.lang.String EXTRA_PRINTER_INFO = "android.intent.extra.print.PRINTER_INFO";
     field public static final java.lang.String EXTRA_PRINT_JOB_INFO = "android.intent.extra.print.PRINT_JOB_INFO";
     field public static final java.lang.String SERVICE_INTERFACE = "android.printservice.PrintService";
     field public static final java.lang.String SERVICE_META_DATA = "android.printservice";
@@ -23125,6 +23299,13 @@
     field public static final java.lang.String URL = "data1";
   }
 
+  public static final class ContactsContract.ContactCounts {
+    ctor public ContactsContract.ContactCounts();
+    field public static final java.lang.String ADDRESS_BOOK_INDEX_EXTRAS = "address_book_index_extras";
+    field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "address_book_index_counts";
+    field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "address_book_index_titles";
+  }
+
   protected static abstract interface ContactsContract.ContactNameColumns {
     field public static final java.lang.String DISPLAY_NAME_ALTERNATIVE = "display_name_alt";
     field public static final java.lang.String DISPLAY_NAME_PRIMARY = "display_name";
@@ -23897,7 +24078,6 @@
   }
 
   public static final class MediaStore.Audio.Radio {
-    ctor public MediaStore.Audio.Radio();
     field public static final java.lang.String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/radio";
   }
 
@@ -25806,6 +25986,36 @@
 
 }
 
+package android.service.fingerprint {
+
+  public class FingerprintManager {
+    ctor public FingerprintManager(android.content.Context);
+    method public void enroll(long);
+    method public boolean enrolledAndEnabled();
+    method public void remove(int);
+    method public void startListening(android.service.fingerprint.FingerprintManagerReceiver);
+    method public void stopListening();
+    field public static final int FINGERPRINT_ERROR = -1; // 0xffffffff
+    field public static final int FINGERPRINT_ERROR_BAD_CAPTURE = 2; // 0x2
+    field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1
+    field public static final int FINGERPRINT_ERROR_NO_RECEIVER = -10; // 0xfffffff6
+    field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4
+    field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3
+    field public static final int FINGERPRINT_SCANNED = 1; // 0x1
+    field public static final int FINGERPRINT_TEMPLATE_ENROLLING = 2; // 0x2
+    field public static final int FINGERPRINT_TEMPLATE_REMOVED = 4; // 0x4
+  }
+
+  public class FingerprintManagerReceiver {
+    ctor public FingerprintManagerReceiver();
+    method public void onEnrollResult(int, int);
+    method public void onError(int);
+    method public void onRemoved(int);
+    method public void onScanned(int, int);
+  }
+
+}
+
 package android.service.notification {
 
   public abstract class NotificationListenerService extends android.app.Service {
@@ -25885,6 +26095,20 @@
 
 }
 
+package android.service.trust {
+
+  public class TrustAgentService extends android.app.Service {
+    ctor public TrustAgentService();
+    method public final void grantTrust(java.lang.CharSequence, long, boolean);
+    method public final android.os.IBinder onBind(android.content.Intent);
+    method public void onUnlockAttempt(boolean);
+    method public final void revokeTrust();
+    field public static final java.lang.String SERVICE_INTERFACE = "android.service.trust.TrustAgentService";
+    field public static final java.lang.String TRUST_AGENT_META_DATA = "android.service.trust.trustagent";
+  }
+
+}
+
 package android.service.voice {
 
   public class VoiceInteractionService extends android.app.Service {
@@ -26094,11 +26318,74 @@
 
 package android.speech.tts {
 
+  public final class Markup implements android.os.Parcelable {
+    ctor public Markup();
+    ctor public Markup(java.lang.String);
+    ctor public Markup(android.speech.tts.Markup);
+    method public android.speech.tts.Markup addNestedMarkup(android.speech.tts.Markup);
+    method public int describeContents();
+    method public android.speech.tts.Markup getNestedMarkup(int);
+    method public java.util.List<android.speech.tts.Markup> getNestedMarkups();
+    method public java.lang.String getParameter(java.lang.String);
+    method public java.lang.String getPlainText();
+    method public java.lang.String getType();
+    method public static android.speech.tts.Markup markupFromString(java.lang.String) throws java.lang.IllegalArgumentException;
+    method public int nestedMarkupSize();
+    method public int parametersSize();
+    method public boolean removeNestedMarkup(android.speech.tts.Markup);
+    method public void removeParameter(java.lang.String);
+    method public android.speech.tts.Markup setParameter(java.lang.String, java.lang.String);
+    method public void setPlainText(java.lang.String);
+    method public void setType(java.lang.String);
+    method public void writeToParcel(android.os.Parcel, int);
+  }
+
+  public final class RequestConfig {
+    method public android.os.Bundle getAudioParams();
+    method public android.speech.tts.VoiceInfo getVoice();
+    method public android.os.Bundle getVoiceParams();
+  }
+
+  public static final class RequestConfig.Builder {
+    method public android.speech.tts.RequestConfig build();
+    method public static android.speech.tts.RequestConfig.Builder newBuilder();
+    method public static android.speech.tts.RequestConfig.Builder newBuilder(android.speech.tts.RequestConfig);
+    method public android.speech.tts.RequestConfig.Builder setAudioParam(java.lang.String, java.lang.Object);
+    method public void setAudioParamPan(float);
+    method public void setAudioParamStream(int);
+    method public void setAudioParamVolume(float);
+    method public android.speech.tts.RequestConfig.Builder setVoice(android.speech.tts.VoiceInfo);
+    method public android.speech.tts.RequestConfig.Builder setVoiceParam(java.lang.String, java.lang.Object);
+  }
+
+  public final class RequestConfigHelper {
+    method public static android.speech.tts.RequestConfig highestQuality(android.speech.tts.TextToSpeechClient.EngineStatus, boolean, android.speech.tts.RequestConfigHelper.VoiceScorer);
+    method public static android.speech.tts.RequestConfig highestQuality(android.speech.tts.TextToSpeechClient.EngineStatus, boolean);
+  }
+
+  public static final class RequestConfigHelper.ExactLocaleMatcher implements android.speech.tts.RequestConfigHelper.VoiceScorer {
+    ctor public RequestConfigHelper.ExactLocaleMatcher(java.util.Locale);
+    method public int scoreVoice(android.speech.tts.VoiceInfo);
+  }
+
+  public static final class RequestConfigHelper.LanguageMatcher implements android.speech.tts.RequestConfigHelper.VoiceScorer {
+    ctor public RequestConfigHelper.LanguageMatcher(java.util.Locale);
+    method public int scoreVoice(android.speech.tts.VoiceInfo);
+  }
+
+  public static abstract interface RequestConfigHelper.VoiceScorer {
+    method public abstract int scoreVoice(android.speech.tts.VoiceInfo);
+  }
+
   public abstract interface SynthesisCallback {
     method public abstract int audioAvailable(byte[], int, int);
     method public abstract int done();
     method public abstract void error();
+    method public abstract void error(int);
+    method public abstract int fallback();
     method public abstract int getMaxBufferSize();
+    method public abstract boolean hasFinished();
+    method public abstract boolean hasStarted();
     method public abstract int start(int, int, int);
   }
 
@@ -26114,7 +26401,19 @@
     method public java.lang.String getVariant();
   }
 
-  public class TextToSpeech {
+  public final class SynthesisRequestV2 implements android.os.Parcelable {
+    ctor public SynthesisRequestV2(android.speech.tts.Markup, java.lang.String, java.lang.String, android.os.Bundle, android.os.Bundle);
+    method public int describeContents();
+    method public android.os.Bundle getAudioParams();
+    method public android.speech.tts.Markup getMarkup();
+    method public java.lang.String getText();
+    method public java.lang.String getUtteranceId();
+    method public java.lang.String getVoiceName();
+    method public android.os.Bundle getVoiceParams();
+    method public void writeToParcel(android.os.Parcel, int);
+  }
+
+  public deprecated class TextToSpeech {
     ctor public TextToSpeech(android.content.Context, android.speech.tts.TextToSpeech.OnInitListener);
     ctor public TextToSpeech(android.content.Context, android.speech.tts.TextToSpeech.OnInitListener, java.lang.String);
     method public int addEarcon(java.lang.String, java.lang.String, int);
@@ -26199,8 +26498,82 @@
     method public abstract void onUtteranceCompleted(java.lang.String);
   }
 
+  public class TextToSpeechClient {
+    ctor public TextToSpeechClient(android.content.Context, java.lang.String, boolean, android.speech.tts.TextToSpeechClient.RequestCallbacks, android.speech.tts.TextToSpeechClient.ConnectionCallbacks);
+    ctor public TextToSpeechClient(android.content.Context, android.speech.tts.TextToSpeechClient.RequestCallbacks, android.speech.tts.TextToSpeechClient.ConnectionCallbacks);
+    method public void connect();
+    method public void disconnect();
+    method public android.speech.tts.TextToSpeechClient.EngineStatus getEngineStatus();
+    method public boolean isConnected();
+    method public boolean isSpeaking();
+    method public void queueAudio(android.net.Uri, android.speech.tts.TextToSpeechClient.UtteranceId, android.speech.tts.RequestConfig, android.speech.tts.TextToSpeechClient.RequestCallbacks);
+    method public void queueSilence(long, android.speech.tts.TextToSpeechClient.UtteranceId, android.speech.tts.TextToSpeechClient.RequestCallbacks);
+    method public void queueSpeak(java.lang.String, android.speech.tts.TextToSpeechClient.UtteranceId, android.speech.tts.RequestConfig, android.speech.tts.TextToSpeechClient.RequestCallbacks);
+    method public void queueSpeak(android.speech.tts.Markup, android.speech.tts.TextToSpeechClient.UtteranceId, android.speech.tts.RequestConfig, android.speech.tts.TextToSpeechClient.RequestCallbacks);
+    method public void queueSynthesizeToFile(java.lang.String, android.speech.tts.TextToSpeechClient.UtteranceId, java.io.File, android.speech.tts.RequestConfig, android.speech.tts.TextToSpeechClient.RequestCallbacks);
+    method public void queueSynthesizeToFile(android.speech.tts.Markup, android.speech.tts.TextToSpeechClient.UtteranceId, java.io.File, android.speech.tts.RequestConfig, android.speech.tts.TextToSpeechClient.RequestCallbacks);
+    method public void stop();
+  }
+
+  public static abstract interface TextToSpeechClient.ConnectionCallbacks {
+    method public abstract void onConnectionFailure();
+    method public abstract void onConnectionSuccess();
+    method public abstract void onEngineStatusChange(android.speech.tts.TextToSpeechClient.EngineStatus);
+    method public abstract void onServiceDisconnected();
+  }
+
+  public static final class TextToSpeechClient.EngineStatus {
+    method public java.lang.String getEnginePackage();
+    method public java.util.List<android.speech.tts.VoiceInfo> getVoices();
+  }
+
+  public static final class TextToSpeechClient.Params {
+    field public static final java.lang.String AUDIO_PARAM_PAN = "pan";
+    field public static final java.lang.String AUDIO_PARAM_STREAM = "streamType";
+    field public static final java.lang.String AUDIO_PARAM_VOLUME = "volume";
+    field public static final java.lang.String FALLBACK_VOICE_NAME = "fallbackVoiceName";
+    field public static final java.lang.String NETWORK_RETRIES_COUNT = "networkRetriesCount";
+    field public static final java.lang.String NETWORK_TIMEOUT_MS = "networkTimeoutMs";
+    field public static final java.lang.String SPEECH_PITCH = "speechPitch";
+    field public static final java.lang.String SPEECH_SPEED = "speechSpeed";
+    field public static final java.lang.String TRACK_SUBUTTERANCE_PROGRESS = "trackSubutteranceProgress";
+  }
+
+  public static abstract class TextToSpeechClient.RequestCallbacks {
+    ctor public TextToSpeechClient.RequestCallbacks();
+    method public void onSynthesisFailure(android.speech.tts.TextToSpeechClient.UtteranceId, int);
+    method public void onSynthesisFallback(android.speech.tts.TextToSpeechClient.UtteranceId);
+    method public void onSynthesisProgress(android.speech.tts.TextToSpeechClient.UtteranceId, int, int);
+    method public void onSynthesisStart(android.speech.tts.TextToSpeechClient.UtteranceId);
+    method public void onSynthesisStop(android.speech.tts.TextToSpeechClient.UtteranceId);
+    method public void onSynthesisSuccess(android.speech.tts.TextToSpeechClient.UtteranceId);
+  }
+
+  public static final class TextToSpeechClient.Status {
+    field public static final int ERROR_DOWNLOADING_ADDITIONAL_DATA = 17; // 0x11
+    field public static final int ERROR_INVALID_REQUEST = 15; // 0xf
+    field public static final int ERROR_NETWORK = 13; // 0xd
+    field public static final int ERROR_NETWORK_TIMEOUT = 14; // 0xe
+    field public static final int ERROR_NON_UNIQUE_UTTERANCE_ID = 16; // 0x10
+    field public static final int ERROR_OUTPUT = 12; // 0xc
+    field public static final int ERROR_SERVICE = 11; // 0xb
+    field public static final int ERROR_SYNTHESIS = 10; // 0xa
+    field public static final int ERROR_UNKNOWN = -1; // 0xffffffff
+    field public static final int STOPPED = 100; // 0x64
+    field public static final int SUCCESS = 0; // 0x0
+  }
+
+  public static class TextToSpeechClient.UtteranceId {
+    ctor public TextToSpeechClient.UtteranceId();
+    method public final java.lang.String toUniqueString();
+  }
+
   public abstract class TextToSpeechService extends android.app.Service {
     ctor public TextToSpeechService();
+    method protected java.util.List<android.speech.tts.VoiceInfo> checkVoicesInfo();
+    method public void forceVoicesInfoCheck();
+    method public android.speech.tts.VoiceInfo getVoicesInfoWithName(java.lang.String);
+    method protected boolean implementsV2API();
     method public android.os.IBinder onBind(android.content.Intent);
     method protected java.util.Set<java.lang.String> onGetFeaturesForLanguage(java.lang.String, java.lang.String, java.lang.String);
     method protected abstract java.lang.String[] onGetLanguage();
@@ -26208,6 +26581,87 @@
     method protected abstract int onLoadLanguage(java.lang.String, java.lang.String, java.lang.String);
     method protected abstract void onStop();
     method protected abstract void onSynthesizeText(android.speech.tts.SynthesisRequest, android.speech.tts.SynthesisCallback);
+    method protected void onSynthesizeTextV2(android.speech.tts.SynthesisRequestV2, android.speech.tts.VoiceInfo, android.speech.tts.SynthesisCallback);
+    method protected void onVoicesInfoChange();
+  }
+
+  public class Utterance {
+    ctor public Utterance();
+    method public android.speech.tts.Utterance append(android.speech.tts.Utterance.AbstractTts<? extends android.speech.tts.Utterance.AbstractTts<?>>);
+    method public android.speech.tts.Utterance append(java.lang.String);
+    method public android.speech.tts.Utterance append(int);
+    method public android.speech.tts.Markup createMarkup();
+    method public android.speech.tts.Utterance.AbstractTts<? extends android.speech.tts.Utterance.AbstractTts<?>> get(int);
+    method public android.speech.tts.Utterance setNoWarningOnFallback(boolean);
+    method public int size();
+    method public static android.speech.tts.Utterance utteranceFromString(java.lang.String) throws java.lang.IllegalArgumentException;
+    field public static final int ANIMACY_ANIMATE = 1; // 0x1
+    field public static final int ANIMACY_INANIMATE = 2; // 0x2
+    field public static final int ANIMACY_UNKNOWN = 0; // 0x0
+    field public static final int CASE_ABLATIVE = 4; // 0x4
+    field public static final int CASE_ACCUSATIVE = 2; // 0x2
+    field public static final int CASE_DATIVE = 3; // 0x3
+    field public static final int CASE_GENITIVE = 5; // 0x5
+    field public static final int CASE_INSTRUMENTAL = 8; // 0x8
+    field public static final int CASE_LOCATIVE = 7; // 0x7
+    field public static final int CASE_NOMINATIVE = 1; // 0x1
+    field public static final int CASE_UNKNOWN = 0; // 0x0
+    field public static final int CASE_VOCATIVE = 6; // 0x6
+    field public static final int GENDER_FEMALE = 3; // 0x3
+    field public static final int GENDER_MALE = 2; // 0x2
+    field public static final int GENDER_NEUTRAL = 1; // 0x1
+    field public static final int GENDER_UNKNOWN = 0; // 0x0
+    field public static final java.lang.String KEY_NO_WARNING_ON_FALLBACK = "no_warning_on_fallback";
+    field public static final int MULTIPLICITY_DUAL = 2; // 0x2
+    field public static final int MULTIPLICITY_PLURAL = 3; // 0x3
+    field public static final int MULTIPLICITY_SINGLE = 1; // 0x1
+    field public static final int MULTIPLICITY_UNKNOWN = 0; // 0x0
+    field public static final java.lang.String TYPE_UTTERANCE = "utterance";
+  }
+
+  public static abstract class Utterance.AbstractTts {
+    ctor protected Utterance.AbstractTts();
+    ctor protected Utterance.AbstractTts(android.speech.tts.Markup);
+    method public java.lang.String generatePlainText();
+    method public android.speech.tts.Markup getMarkup();
+    method protected java.lang.String getParameter(java.lang.String);
+    method public java.lang.String getPlainText();
+    method public java.lang.String getType();
+    method protected C removeParameter(java.lang.String);
+    method protected C setParameter(java.lang.String, java.lang.String);
+    method public C setPlainText(java.lang.String);
+    field protected android.speech.tts.Markup mMarkup;
+  }
+
+  public static abstract class Utterance.AbstractTtsSemioticClass extends android.speech.tts.Utterance.AbstractTts {
+    ctor protected Utterance.AbstractTtsSemioticClass();
+    ctor protected Utterance.AbstractTtsSemioticClass(android.speech.tts.Markup);
+    method public int getAnimacy();
+    method public int getCase();
+    method public int getGender();
+    method public int getMultiplicity();
+    method public C setAnimacy(int);
+    method public C setCase(int);
+    method public C setGender(int);
+    method public C setMultiplicity(int);
+  }
+
+  public static class Utterance.TtsCardinal extends android.speech.tts.Utterance.AbstractTtsSemioticClass {
+    ctor public Utterance.TtsCardinal();
+    ctor public Utterance.TtsCardinal(int);
+    ctor public Utterance.TtsCardinal(java.lang.String);
+    method public java.lang.String getInteger();
+    method public android.speech.tts.Utterance.TtsCardinal setInteger(int);
+    method public android.speech.tts.Utterance.TtsCardinal setInteger(java.lang.String);
+    field protected static final java.lang.String TYPE_CARDINAL = "cardinal";
+  }
+
+  public static class Utterance.TtsText extends android.speech.tts.Utterance.AbstractTtsSemioticClass {
+    ctor public Utterance.TtsText();
+    ctor public Utterance.TtsText(java.lang.String);
+    method public java.lang.String getText();
+    method public android.speech.tts.Utterance.TtsText setText(java.lang.String);
+    field protected static final java.lang.String TYPE_TEXT = "text";
   }
 
   public abstract class UtteranceProgressListener {
@@ -26217,6 +26671,44 @@
     method public abstract void onStart(java.lang.String);
   }
 
+  public final class VoiceInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.os.Bundle getAdditionalFeatures();
+    method public int getLatency();
+    method public java.util.Locale getLocale();
+    method public java.lang.String getName();
+    method public android.os.Bundle getParamsWithDefaults();
+    method public int getQuality();
+    method public boolean getRequiresNetworkConnection();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final java.lang.String FEATURE_MAY_AUTOINSTALL = "mayAutoInstall";
+    field public static final java.lang.String FEATURE_SPEAKER_GENDER = "speakerGender";
+    field public static final java.lang.String FEATURE_WORDS_PER_MINUTE = "wordsPerMinute";
+    field public static final int LATENCY_HIGH = 400; // 0x190
+    field public static final int LATENCY_LOW = 200; // 0xc8
+    field public static final int LATENCY_NORMAL = 300; // 0x12c
+    field public static final int LATENCY_VERY_HIGH = 500; // 0x1f4
+    field public static final int LATENCY_VERY_LOW = 100; // 0x64
+    field public static final int QUALITY_HIGH = 400; // 0x190
+    field public static final int QUALITY_LOW = 200; // 0xc8
+    field public static final int QUALITY_NORMAL = 300; // 0x12c
+    field public static final int QUALITY_VERY_HIGH = 500; // 0x1f4
+    field public static final int QUALITY_VERY_LOW = 100; // 0x64
+  }
+
+  public static final class VoiceInfo.Builder {
+    ctor public VoiceInfo.Builder();
+    ctor public VoiceInfo.Builder(android.speech.tts.VoiceInfo);
+    method public android.speech.tts.VoiceInfo build();
+    method public android.speech.tts.VoiceInfo.Builder setAdditionalFeatures(android.os.Bundle);
+    method public android.speech.tts.VoiceInfo.Builder setLatency(int);
+    method public android.speech.tts.VoiceInfo.Builder setLocale(java.util.Locale);
+    method public android.speech.tts.VoiceInfo.Builder setName(java.lang.String);
+    method public android.speech.tts.VoiceInfo.Builder setParamsWithDefaults(android.os.Bundle);
+    method public android.speech.tts.VoiceInfo.Builder setQuality(int);
+    method public android.speech.tts.VoiceInfo.Builder setRequiresNetworkConnection(boolean);
+  }
+
 }
 
 package android.system {
@@ -26850,6 +27342,326 @@
 
 }
 
+package android.telecomm {
+
+  public final class CallAudioState implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+    field public static int ROUTE_ALL;
+    field public static int ROUTE_BLUETOOTH;
+    field public static int ROUTE_EARPIECE;
+    field public static int ROUTE_SPEAKER;
+    field public static int ROUTE_WIRED_HEADSET;
+    field public static int ROUTE_WIRED_OR_EARPIECE;
+    field public final boolean isMuted;
+    field public final int route;
+    field public final int supportedRouteMask;
+  }
+
+  public class CallCapabilities {
+    ctor public CallCapabilities();
+    field public static final int ADD_CALL = 16; // 0x10
+    field public static final int ALL = 511; // 0x1ff
+    field public static final int CONNECTION_HANDOFF = 256; // 0x100
+    field public static final int GENERIC_CONFERENCE = 128; // 0x80
+    field public static final int HOLD = 1; // 0x1
+    field public static final int MERGE_CALLS = 4; // 0x4
+    field public static final int MUTE = 64; // 0x40
+    field public static final int RESPOND_VIA_TEXT = 32; // 0x20
+    field public static final int SUPPORT_HOLD = 2; // 0x2
+    field public static final int SWAP_CALLS = 8; // 0x8
+  }
+
+  public final class CallInfo implements android.os.Parcelable {
+    ctor public CallInfo(java.lang.String, android.telecomm.CallState, android.net.Uri);
+    method public int describeContents();
+    method public android.telecomm.CallServiceDescriptor getCurrentCallServiceDescriptor();
+    method public android.os.Bundle getExtras();
+    method public android.telecomm.GatewayInfo getGatewayInfo();
+    method public android.net.Uri getHandle();
+    method public java.lang.String getId();
+    method public android.net.Uri getOriginalHandle();
+    method public android.telecomm.CallState getState();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
+  public final class CallNumberPresentation extends java.lang.Enum {
+    method public static android.telecomm.CallNumberPresentation valueOf(java.lang.String);
+    method public static final android.telecomm.CallNumberPresentation[] values();
+    enum_constant public static final android.telecomm.CallNumberPresentation ALLOWED;
+    enum_constant public static final android.telecomm.CallNumberPresentation PAYPHONE;
+    enum_constant public static final android.telecomm.CallNumberPresentation RESTRICTED;
+    enum_constant public static final android.telecomm.CallNumberPresentation UNKNOWN;
+  }
+
+  public abstract class CallService extends android.app.Service {
+    ctor public CallService();
+    method public abstract void abort(java.lang.String);
+    method public abstract void answer(java.lang.String);
+    method public abstract void call(android.telecomm.CallInfo);
+    method public abstract void disconnect(java.lang.String);
+    method protected final android.telecomm.CallServiceAdapter getAdapter();
+    method public final android.os.IBinder getBinder();
+    method public abstract void hold(java.lang.String);
+    method public abstract void isCompatibleWith(android.telecomm.CallInfo);
+    method protected void onAdapterAttached(android.telecomm.CallServiceAdapter);
+    method public abstract void onAudioStateChanged(java.lang.String, android.telecomm.CallAudioState);
+    method public final android.os.IBinder onBind(android.content.Intent);
+    method public void onPostDialContinue(java.lang.String, boolean);
+    method public void onPostDialWait(android.telecomm.Connection, java.lang.String);
+    method public abstract void playDtmfTone(java.lang.String, char);
+    method public abstract void reject(java.lang.String);
+    method public abstract void setIncomingCallId(java.lang.String, android.os.Bundle);
+    method public abstract void stopDtmfTone(java.lang.String);
+    method public abstract void unhold(java.lang.String);
+  }
+
+  public final class CallServiceAdapter {
+    method public void handleFailedOutgoingCall(android.telecomm.ConnectionRequest, int, java.lang.String);
+    method public void handleSuccessfulOutgoingCall(java.lang.String);
+    method public void handoffCall(java.lang.String);
+    method public void notifyIncomingCall(android.telecomm.CallInfo);
+    method public void onPostDialWait(java.lang.String, java.lang.String);
+    method public void setActive(java.lang.String);
+    method public void setDialing(java.lang.String);
+    method public void setDisconnected(java.lang.String, int, java.lang.String);
+    method public void setIsCompatibleWith(java.lang.String, boolean);
+    method public void setOnHold(java.lang.String);
+    method public void setRequestingRingback(java.lang.String, boolean);
+    method public void setRinging(java.lang.String);
+  }
+
+  public final class CallServiceDescriptor implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.String getCallServiceId();
+    method public int getNetworkType();
+    method public android.content.ComponentName getServiceComponent();
+    method public static android.telecomm.CallServiceDescriptor.Builder newBuilder(android.content.Context);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+    field public static final int FLAG_MOBILE = 4; // 0x4
+    field public static final int FLAG_PSTN = 2; // 0x2
+    field public static final int FLAG_WIFI = 1; // 0x1
+  }
+
+  public static class CallServiceDescriptor.Builder {
+    method public android.telecomm.CallServiceDescriptor build();
+    method public android.telecomm.CallServiceDescriptor.Builder setCallService(java.lang.Class<? extends android.telecomm.CallService>);
+    method public android.telecomm.CallServiceDescriptor.Builder setNetworkType(int);
+  }
+
+  public final class CallServiceLookupResponse {
+    method public void setCallServiceDescriptors(java.util.List<android.telecomm.CallServiceDescriptor>);
+  }
+
+  public abstract class CallServiceProvider extends android.app.Service {
+    ctor protected CallServiceProvider();
+    method public abstract void lookupCallServices(android.telecomm.CallServiceLookupResponse);
+    method public android.os.IBinder onBind(android.content.Intent);
+  }
+
+  public abstract class CallServiceSelector extends android.app.Service {
+    ctor protected CallServiceSelector();
+    method protected final void cancelOutgoingCall(android.telecomm.CallInfo);
+    method protected final android.telecomm.CallServiceSelectorAdapter getAdapter();
+    method protected final java.util.Collection<android.telecomm.CallInfo> getCalls();
+    method protected void onAdapterAttached(android.telecomm.CallServiceSelectorAdapter);
+    method public final android.os.IBinder onBind(android.content.Intent);
+    method protected abstract void select(android.telecomm.CallInfo, java.util.List<android.telecomm.CallServiceDescriptor>);
+  }
+
+  public final class CallServiceSelectorAdapter {
+    method public void cancelOutgoingCall(java.lang.String);
+    method public void setHandoffInfo(java.lang.String, android.net.Uri, android.os.Bundle);
+    method public void setSelectedCallServices(java.lang.String, java.util.List<android.telecomm.CallServiceDescriptor>);
+  }
+
+  public final class CallState extends java.lang.Enum {
+    method public static android.telecomm.CallState valueOf(java.lang.String);
+    method public static final android.telecomm.CallState[] values();
+    enum_constant public static final android.telecomm.CallState ACTIVE;
+    enum_constant public static final android.telecomm.CallState DIALING;
+    enum_constant public static final android.telecomm.CallState DISCONNECTED;
+    enum_constant public static final android.telecomm.CallState NEW;
+    enum_constant public static final android.telecomm.CallState ON_HOLD;
+    enum_constant public static final android.telecomm.CallState POST_DIAL;
+    enum_constant public static final android.telecomm.CallState POST_DIAL_WAIT;
+    enum_constant public static final android.telecomm.CallState RINGING;
+  }
+
+  public abstract class Connection {
+    ctor protected Connection();
+    method public final android.telecomm.CallAudioState getCallAudioState();
+    method public final android.net.Uri getHandle();
+    method public boolean isRequestingRingback();
+    method protected void onAbort();
+    method protected void onAnswer();
+    method protected void onDisconnect();
+    method protected void onHold();
+    method protected void onPlayDtmfTone(char);
+    method protected void onPostDialContinue(boolean);
+    method protected void onReject();
+    method protected void onSetAudioState(android.telecomm.CallAudioState);
+    method protected void onSetSignal(android.os.Bundle);
+    method protected void onSetState(int);
+    method protected void onStopDtmfTone();
+    method protected void onUnhold();
+    method protected void setActive();
+    method public void setAudioState(android.telecomm.CallAudioState);
+    method protected void setDialing();
+    method protected void setDisconnected(int, java.lang.String);
+    method protected void setHandle(android.net.Uri);
+    method protected void setOnHold();
+    method protected void setRequestingRingback(boolean);
+    method protected void setRinging();
+    method public static java.lang.String stateToString(int);
+  }
+
+  public static abstract interface Connection.Listener {
+    method public abstract void onAudioStateChanged(android.telecomm.Connection, android.telecomm.CallAudioState);
+    method public abstract void onDestroyed(android.telecomm.Connection);
+    method public abstract void onDisconnected(android.telecomm.Connection, int, java.lang.String);
+    method public abstract void onHandleChanged(android.telecomm.Connection, android.net.Uri);
+    method public abstract void onRequestingRingback(android.telecomm.Connection, boolean);
+    method public abstract void onSignalChanged(android.telecomm.Connection, android.os.Bundle);
+    method public abstract void onStateChanged(android.telecomm.Connection, int);
+  }
+
+  public static class Connection.ListenerBase implements android.telecomm.Connection.Listener {
+    ctor public Connection.ListenerBase();
+    method public void onAudioStateChanged(android.telecomm.Connection, android.telecomm.CallAudioState);
+    method public void onDestroyed(android.telecomm.Connection);
+    method public void onDisconnected(android.telecomm.Connection, int, java.lang.String);
+    method public void onHandleChanged(android.telecomm.Connection, android.net.Uri);
+    method public void onRequestingRingback(android.telecomm.Connection, boolean);
+    method public void onSignalChanged(android.telecomm.Connection, android.os.Bundle);
+    method public void onStateChanged(android.telecomm.Connection, int);
+  }
+
+  public final class Connection.State {
+    field public static final int ACTIVE = 3; // 0x3
+    field public static final int DIALING = 2; // 0x2
+    field public static final int DISCONNECTED = 5; // 0x5
+    field public static final int HOLDING = 4; // 0x4
+    field public static final int NEW = 0; // 0x0
+    field public static final int RINGING = 1; // 0x1
+  }
+
+  public final class ConnectionRequest implements android.os.Parcelable {
+    ctor public ConnectionRequest(android.net.Uri, android.os.Bundle);
+    ctor public ConnectionRequest(java.lang.String, android.net.Uri, android.os.Bundle);
+    method public int describeContents();
+    method public java.lang.String getCallId();
+    method public android.os.Bundle getExtras();
+    method public android.net.Uri getHandle();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
+  public abstract class ConnectionService extends android.telecomm.CallService {
+    ctor public ConnectionService();
+    method public final void abort(java.lang.String);
+    method public final void answer(java.lang.String);
+    method public final void call(android.telecomm.CallInfo);
+    method public final void disconnect(java.lang.String);
+    method public final void hold(java.lang.String);
+    method public final void isCompatibleWith(android.telecomm.CallInfo);
+    method public final void onAudioStateChanged(java.lang.String, android.telecomm.CallAudioState);
+    method public void onCreateConnections(android.telecomm.ConnectionRequest, android.telecomm.Response<android.telecomm.ConnectionRequest, android.telecomm.Connection>);
+    method public void onCreateIncomingConnection(android.telecomm.ConnectionRequest, android.telecomm.Response<android.telecomm.ConnectionRequest, android.telecomm.Connection>);
+    method public void onFindSubscriptions(android.net.Uri, android.telecomm.Response<android.net.Uri, android.telecomm.Subscription>);
+    method public final void onPostDialContinue(java.lang.String, boolean);
+    method public final void onPostDialWait(android.telecomm.Connection, java.lang.String);
+    method public final void playDtmfTone(java.lang.String, char);
+    method public final void reject(java.lang.String);
+    method public final void setIncomingCallId(java.lang.String, android.os.Bundle);
+    method public final void stopDtmfTone(java.lang.String);
+    method public final void unhold(java.lang.String);
+  }
+
+  public class GatewayInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.net.Uri getGatewayHandle();
+    method public java.lang.String getGatewayProviderPackageName();
+    method public android.net.Uri getOriginalHandle();
+    method public boolean isEmpty();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
+  public final class InCallAdapter {
+    method public void answerCall(java.lang.String);
+    method public void disconnectCall(java.lang.String);
+    method public void handoffCall(java.lang.String);
+    method public void holdCall(java.lang.String);
+    method public void mute(boolean);
+    method public void playDtmfTone(java.lang.String, char);
+    method public void postDialContinue(java.lang.String, boolean);
+    method public void rejectCall(java.lang.String);
+    method public void setAudioRoute(int);
+    method public void stopDtmfTone(java.lang.String);
+    method public void unholdCall(java.lang.String);
+  }
+
+  public final class InCallCall implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getCapabilities();
+    method public long getConnectTimeMillis();
+    method public android.telecomm.CallServiceDescriptor getCurrentCallServiceDescriptor();
+    method public int getDisconnectCauseCode();
+    method public java.lang.String getDisconnectCauseMsg();
+    method public android.telecomm.GatewayInfo getGatewayInfo();
+    method public android.net.Uri getHandle();
+    method public android.telecomm.CallServiceDescriptor getHandoffCallServiceDescriptor();
+    method public java.lang.String getId();
+    method public android.telecomm.CallState getState();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
+  public abstract class InCallService extends android.app.Service {
+    ctor protected InCallService();
+    method protected abstract void addCall(android.telecomm.InCallCall);
+    method protected abstract void bringToForeground(boolean);
+    method protected final android.telecomm.InCallAdapter getAdapter();
+    method protected void onAdapterAttached(android.telecomm.InCallAdapter);
+    method protected abstract void onAudioStateChanged(android.telecomm.CallAudioState);
+    method public final android.os.IBinder onBind(android.content.Intent);
+    method protected abstract void setPostDial(java.lang.String, java.lang.String);
+    method protected abstract void setPostDialWait(java.lang.String, java.lang.String);
+    method protected abstract void updateCall(android.telecomm.InCallCall);
+  }
+
+  public abstract interface Response {
+    method public abstract void onError(IN, int, java.lang.String);
+    method public abstract void onResult(IN, OUT...);
+  }
+
+  public class Subscription implements android.os.Parcelable {
+    ctor public Subscription();
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
+  public final class TelecommConstants {
+    ctor public TelecommConstants();
+    field public static final java.lang.String ACTION_CALL_SERVICE;
+    field public static final java.lang.String ACTION_CALL_SERVICE_PROVIDER;
+    field public static final java.lang.String ACTION_CALL_SERVICE_SELECTOR;
+    field public static final java.lang.String ACTION_INCOMING_CALL = "android.intent.action.INCOMING_CALL";
+    field public static final char DTMF_CHARACTER_PAUSE = 44; // 0x002c ','
+    field public static final char DTMF_CHARACTER_WAIT = 59; // 0x003b ';'
+    field public static final java.lang.String EXTRA_CALL_DISCONNECT_CAUSE = "android.telecomm.extra.CALL_DISCONNECT_CAUSE";
+    field public static final java.lang.String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecomm.extra.CALL_DISCONNECT_MESSAGE";
+    field public static final java.lang.String EXTRA_CALL_SERVICE_DESCRIPTOR = "android.intent.extra.CALL_SERVICE_DESCRIPTOR";
+    field public static final java.lang.String EXTRA_INCOMING_CALL_EXTRAS = "android.intent.extra.INCOMING_CALL_EXTRAS";
+  }
+
+}
+
 package android.telephony {
 
   public final class CellIdentityCdma implements android.os.Parcelable {
@@ -26998,6 +27810,55 @@
     field public static final android.os.Parcelable.Creator CREATOR;
   }
 
+  public class DisconnectCause {
+    method public static java.lang.String toString(int);
+    field public static final int BUSY = 4; // 0x4
+    field public static final int CALL_BARRED = 20; // 0x14
+    field public static final int CDMA_ACCESS_BLOCKED = 35; // 0x23
+    field public static final int CDMA_ACCESS_FAILURE = 32; // 0x20
+    field public static final int CDMA_CALL_LOST = 41; // 0x29
+    field public static final int CDMA_DROP = 27; // 0x1b
+    field public static final int CDMA_INTERCEPT = 28; // 0x1c
+    field public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE = 26; // 0x1a
+    field public static final int CDMA_NOT_EMERGENCY = 34; // 0x22
+    field public static final int CDMA_PREEMPTED = 33; // 0x21
+    field public static final int CDMA_REORDER = 29; // 0x1d
+    field public static final int CDMA_RETRY_ORDER = 31; // 0x1f
+    field public static final int CDMA_SO_REJECT = 30; // 0x1e
+    field public static final int CONGESTION = 5; // 0x5
+    field public static final int CS_RESTRICTED = 22; // 0x16
+    field public static final int CS_RESTRICTED_EMERGENCY = 24; // 0x18
+    field public static final int CS_RESTRICTED_NORMAL = 23; // 0x17
+    field public static final int DIALED_MMI = 39; // 0x27
+    field public static final int EMERGENCY_ONLY = 37; // 0x25
+    field public static final int ERROR_UNSPECIFIED = 36; // 0x24
+    field public static final int FDN_BLOCKED = 21; // 0x15
+    field public static final int ICC_ERROR = 19; // 0x13
+    field public static final int INCOMING_MISSED = 1; // 0x1
+    field public static final int INCOMING_REJECTED = 16; // 0x10
+    field public static final int INVALID_CREDENTIALS = 10; // 0xa
+    field public static final int INVALID_NUMBER = 7; // 0x7
+    field public static final int LIMIT_EXCEEDED = 15; // 0xf
+    field public static final int LOCAL = 3; // 0x3
+    field public static final int LOST_SIGNAL = 14; // 0xe
+    field public static final int MAXIMUM_VALID_VALUE = 42; // 0x2a
+    field public static final int MINIMUM_VALID_VALUE = 0; // 0x0
+    field public static final int MMI = 6; // 0x6
+    field public static final int NORMAL = 2; // 0x2
+    field public static final int NOT_DISCONNECTED = 0; // 0x0
+    field public static final int NOT_VALID = -1; // 0xffffffff
+    field public static final int NO_PHONE_NUMBER_SUPPLIED = 38; // 0x26
+    field public static final int NUMBER_UNREACHABLE = 8; // 0x8
+    field public static final int OUT_OF_NETWORK = 11; // 0xb
+    field public static final int OUT_OF_SERVICE = 18; // 0x12
+    field public static final int POWER_OFF = 17; // 0x11
+    field public static final int SERVER_ERROR = 12; // 0xc
+    field public static final int SERVER_UNREACHABLE = 9; // 0x9
+    field public static final int TIMED_OUT = 13; // 0xd
+    field public static final int UNOBTAINABLE_NUMBER = 25; // 0x19
+    field public static final int VOICEMAIL_NUMBER_MISSING = 40; // 0x28
+  }
+
   public class NeighboringCellInfo implements android.os.Parcelable {
     ctor public deprecated NeighboringCellInfo();
     ctor public deprecated NeighboringCellInfo(int, int);
@@ -31855,6 +32716,7 @@
     method public void requestLayout();
     method public boolean requestRectangleOnScreen(android.graphics.Rect);
     method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
+    method public final void requestUnbufferedDispatch(android.view.MotionEvent);
     method public static int resolveSize(int, int);
     method public static int resolveSizeAndState(int, int, int);
     method public void restoreHierarchyState(android.util.SparseArray<android.os.Parcelable>);
@@ -32663,6 +33525,7 @@
     method public android.transition.Transition getSharedElementEnterTransition();
     method public android.transition.Transition getSharedElementExitTransition();
     method public abstract int getStatusBarColor();
+    method public long getTransitionBackgroundFadeDuration();
     method public android.transition.TransitionManager getTransitionManager();
     method public abstract int getVolumeControlStream();
     method public android.view.WindowManager getWindowManager();
@@ -32721,6 +33584,7 @@
     method public abstract void setStatusBarColor(int);
     method public abstract void setTitle(java.lang.CharSequence);
     method public abstract deprecated void setTitleColor(int);
+    method public void setTransitionBackgroundFadeDuration(long);
     method public void setTransitionManager(android.transition.TransitionManager);
     method public void setType(int);
     method public void setUiOptions(int);
@@ -35562,6 +36426,10 @@
     method public void setRowCount(int);
     method public void setRowOrderPreserved(boolean);
     method public void setUseDefaultMargins(boolean);
+    method public static android.widget.GridLayout.Spec spec(int, int, android.widget.GridLayout.Alignment, float);
+    method public static android.widget.GridLayout.Spec spec(int, android.widget.GridLayout.Alignment, float);
+    method public static android.widget.GridLayout.Spec spec(int, int, float);
+    method public static android.widget.GridLayout.Spec spec(int, float);
     method public static android.widget.GridLayout.Spec spec(int, int, android.widget.GridLayout.Alignment);
     method public static android.widget.GridLayout.Spec spec(int, android.widget.GridLayout.Alignment);
     method public static android.widget.GridLayout.Spec spec(int, int);
diff --git a/api/removed.txt b/api/removed.txt
index e69de29..458c422 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -0,0 +1,8 @@
+package android.media {
+
+  public class AudioFormat {
+    ctor public AudioFormat();
+  }
+
+}
+
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 6b55b7b..8945526 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -35,6 +35,7 @@
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
@@ -46,6 +47,8 @@
 import android.view.IWindowManager;
 import com.android.internal.os.BaseCommand;
 
+import dalvik.system.VMRuntime;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -94,7 +97,11 @@
                 "       am broadcast [--user <USER_ID> | all | current] <INTENT>\n" +
                 "       am instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]\n" +
                 "               [--user <USER_ID> | current]\n" +
-                "               [--no-window-animation] <COMPONENT>\n" +
+                "               [--no-window-animation]\n" +
+                "               [--abi <ABI>]\n : Launch the instrumented process with the "  +
+                "                   selected ABI. This assumes that the process supports the" +
+                "                   selected ABI." +
+                "               <COMPONENT>\n" +
                 "       am profile start [--user <USER_ID> current] <PROCESS> <FILE>\n" +
                 "       am profile stop [--user <USER_ID> current] [<PROCESS>]\n" +
                 "       am dumpheap [--user <USER_ID> current] [-n] <PROCESS> <FILE>\n" +
@@ -835,6 +842,7 @@
         Bundle args = new Bundle();
         String argKey = null, argValue = null;
         IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+        String abi = null;
 
         String opt;
         while ((opt=nextOption()) != null) {
@@ -853,6 +861,8 @@
                 no_window_animation = true;
             } else if (opt.equals("--user")) {
                 userId = parseUserArg(nextArgRequired());
+            } else if (opt.equals("--abi")) {
+                abi = nextArgRequired();
             } else {
                 System.err.println("Error: Unknown option: " + opt);
                 return;
@@ -883,7 +893,24 @@
             wm.setAnimationScale(1, 0.0f);
         }
 
-        if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId)) {
+        if (abi != null) {
+            final String[] supportedAbis = Build.SUPPORTED_ABIS;
+            boolean matched = false;
+            for (String supportedAbi : supportedAbis) {
+                if (supportedAbi.equals(abi)) {
+                    matched = true;
+                    break;
+                }
+            }
+
+            if (!matched) {
+                throw new AndroidException(
+                        "INSTRUMENTATION_FAILED: Unsupported instruction set " + abi);
+            }
+        }
+
+        if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId,
+                abi)) {
             throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
         }
 
diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java
index 2673031..4503726 100644
--- a/cmds/bu/src/com/android/commands/bu/Backup.java
+++ b/cmds/bu/src/com/android/commands/bu/Backup.java
@@ -20,6 +20,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.system.OsConstants;
 import android.util.Log;
 
 import java.io.IOException;
@@ -50,13 +51,11 @@
             return;
         }
 
-        int socketFd = Integer.parseInt(nextArg());
-
         String arg = nextArg();
         if (arg.equals("backup")) {
-            doFullBackup(socketFd);
+            doFullBackup(OsConstants.STDOUT_FILENO);
         } else if (arg.equals("restore")) {
-            doFullRestore(socketFd);
+            doFullRestore(OsConstants.STDIN_FILENO);
         } else {
             Log.e(TAG, "Invalid operation '" + arg + "'");
         }
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 5454b46..47047b8 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -39,6 +39,7 @@
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.IUserManager;
 import android.os.Process;
@@ -57,7 +58,6 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.WeakHashMap;
-
 import javax.crypto.SecretKey;
 import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
@@ -823,6 +823,7 @@
         byte[] tag = null;
         String originatingUriString = null;
         String referrer = null;
+        String abi = null;
 
         while ((opt=nextOption()) != null) {
             if (opt.equals("-l")) {
@@ -893,12 +894,34 @@
                     System.err.println("Error: must supply argument for --referrer");
                     return;
                 }
+            } else if (opt.equals("--abi")) {
+                abi = nextOptionData();
+                if (abi == null) {
+                    System.err.println("Error: must supply argument for --abi");
+                    return;
+                }
             } else {
                 System.err.println("Error: Unknown option: " + opt);
                 return;
             }
         }
 
+        if (abi != null) {
+            final String[] supportedAbis = Build.SUPPORTED_ABIS;
+            boolean matched = false;
+            for (String supportedAbi : supportedAbis) {
+                if (supportedAbi.equals(abi)) {
+                    matched = true;
+                    break;
+                }
+            }
+
+            if (!matched) {
+                System.err.println("Error: abi " + abi + " not supported on this device.");
+                return;
+            }
+        }
+
         final ContainerEncryptionParams encryptionParams;
         if (algo != null || iv != null || key != null || macAlgo != null || macKey != null
                 || tag != null) {
@@ -976,8 +999,9 @@
             VerificationParams verificationParams = new VerificationParams(verificationURI,
                     originatingURI, referrerURI, VerificationParams.NO_UID, null);
 
-            mPm.installPackageWithVerificationAndEncryptionEtc(apkURI, null, obs, installFlags,
-                    installerPackageName, verificationParams, encryptionParams);
+            mPm.installPackageWithVerificationEncryptionAndAbiOverrideEtc(apkURI, null,
+                    obs, installFlags, installerPackageName, verificationParams,
+                    encryptionParams, abi);
 
             synchronized (obs) {
                 while (!obs.finished) {
@@ -1146,12 +1170,22 @@
     }
 
     private void runUninstall() {
-        int unInstallFlags = PackageManager.DELETE_ALL_USERS;
+        int unInstallFlags = 0;
+        int userId = UserHandle.USER_ALL;
 
         String opt;
         while ((opt=nextOption()) != null) {
             if (opt.equals("-k")) {
                 unInstallFlags |= PackageManager.DELETE_KEEP_DATA;
+            } else if (opt.equals("--user")) {
+                String param = nextArg();
+                if (isNumber(param)) {
+                    userId = Integer.parseInt(param);
+                } else {
+                    showUsage();
+                    System.err.println("Error: Invalid user: " + param);
+                    return;
+                }
             } else {
                 System.err.println("Error: Unknown option: " + opt);
                 return;
@@ -1164,7 +1198,34 @@
             showUsage();
             return;
         }
-        boolean result = deletePackage(pkg, unInstallFlags);
+
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_OWNER;
+            unInstallFlags |= PackageManager.DELETE_ALL_USERS;
+        } else {
+            PackageInfo info;
+            try {
+                info = mPm.getPackageInfo(pkg, 0, userId);
+            } catch (RemoteException e) {
+                System.err.println(e.toString());
+                System.err.println(PM_NOT_RUNNING_ERR);
+                return;
+            }
+            if (info == null) {
+                System.err.println("Failure - not installed for " + userId);
+                return;
+            }
+            final boolean isSystem =
+                    (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+            // If we are being asked to delete a system app for just one
+            // user set flag so it disables rather than reverting to system
+            // version of the app.
+            if (isSystem) {
+                unInstallFlags |= PackageManager.DELETE_SYSTEM_APP;
+            }
+        }
+
+        boolean result = deletePackage(pkg, unInstallFlags, userId);
         if (result) {
             System.out.println("Success");
         } else {
@@ -1172,10 +1233,10 @@
         }
     }
 
-    private boolean deletePackage(String pkg, int unInstallFlags) {
+    private boolean deletePackage(String pkg, int unInstallFlags, int userId) {
         PackageDeleteObserver obs = new PackageDeleteObserver();
         try {
-            mPm.deletePackageAsUser(pkg, obs, UserHandle.USER_OWNER, unInstallFlags);
+            mPm.deletePackageAsUser(pkg, obs, userId, unInstallFlags);
 
             synchronized (obs) {
                 while (!obs.finished) {
@@ -1571,7 +1632,7 @@
         System.err.println("       pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f]");
         System.err.println("                  [--algo <algorithm name> --key <key-in-hex> --iv <IV-in-hex>]");
         System.err.println("                  [--originating-uri <URI>] [--referrer <URI>] PATH");
-        System.err.println("       pm uninstall [-k] PACKAGE");
+        System.err.println("       pm uninstall [-k] [--user USER_ID] PACKAGE");
         System.err.println("       pm clear [--user USER_ID] PACKAGE");
         System.err.println("       pm enable [--user USER_ID] PACKAGE_OR_COMPONENT");
         System.err.println("       pm disable [--user USER_ID] PACKAGE_OR_COMPONENT");
@@ -1585,7 +1646,7 @@
         System.err.println("       pm get-install-location");
         System.err.println("       pm set-permission-enforced PERMISSION [true|false]");
         System.err.println("       pm trim-caches DESIRED_FREE_SPACE");
-        System.err.println("       pm create-user [--relatedTo USER_ID] [--managed] USER_NAME");
+        System.err.println("       pm create-user [--profileOf USER_ID] [--managed] USER_NAME");
         System.err.println("       pm remove-user USER_ID");
         System.err.println("       pm get-max-users");
         System.err.println("");
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 2efe4d3..6296dd1 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -141,7 +141,7 @@
 
     ScreenshotClient screenshot;
     sp<IBinder> display = SurfaceComposerClient::getBuiltInDisplay(displayId);
-    if (display != NULL && screenshot.update(display, false) == NO_ERROR) {
+    if (display != NULL && screenshot.update(display, Rect(), false) == NO_ERROR) {
         base = screenshot.getPixels();
         w = screenshot.getWidth();
         h = screenshot.getHeight();
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 2620c44..cbc8150 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -462,7 +462,9 @@
      * anything behind it, then only the modal window will be reported
      * (assuming it is the top one). For convenience the returned windows
      * are ordered in a descending layer order, which is the windows that
-     * are higher in the Z-order are reported first.
+     * are higher in the Z-order are reported first. Since the user can always
+     * interact with the window that has input focus by typing, the focused
+     * window is always returned (even if covered by a modal window).
      * <p>
      * <strong>Note:</strong> In order to access the windows your service has
      * to declare the capability to retrieve window content by setting the
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 4f9ba59..4edb0c6 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -286,8 +286,8 @@
     /**
      * This flag indicates to the system that the accessibility service wants
      * to access content of all interactive windows. An interactive window is a
-     * window that can be touched by a sighted user when explore by touch is not
-     * enabled. If this flag is not set your service will not receive
+     * window that has input focus or can be touched by a sighted user when explore
+     * by touch is not enabled. If this flag is not set your service will not receive
      * {@link android.view.accessibility.AccessibilityEvent#TYPE_WINDOWS_CHANGED}
      * events, calling AccessibilityService{@link AccessibilityService#getWindows()
      * AccessibilityService.getWindows()} will return an empty list, and {@link
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 933135d..06f5aca 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -17,6 +17,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.content.res.Resources.NotFoundException;
@@ -25,6 +26,9 @@
 import android.util.TypedValue;
 import android.util.Xml;
 import android.view.animation.AnimationUtils;
+
+import com.android.internal.R;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -66,11 +70,26 @@
      */
     public static Animator loadAnimator(Context context, int id)
             throws NotFoundException {
+        return loadAnimator(context.getResources(), context.getTheme(), id);
+    }
+
+    /**
+     * Loads an {@link Animator} object from a resource
+     *
+     * @param resources The resources
+     * @param theme The theme
+     * @param id The resource id of the animation to load
+     * @return The animator object reference by the specified id
+     * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded
+     * @hide
+     */
+    public static Animator loadAnimator(Resources resources, Theme theme, int id)
+            throws NotFoundException {
 
         XmlResourceParser parser = null;
         try {
-            parser = context.getResources().getAnimation(id);
-            return createAnimatorFromXml(context, parser);
+            parser = resources.getAnimation(id);
+            return createAnimatorFromXml(resources, theme, parser);
         } catch (XmlPullParserException ex) {
             Resources.NotFoundException rnf =
                     new Resources.NotFoundException("Can't load animation resource ID #0x" +
@@ -150,7 +169,8 @@
 
                         }
                         if (animator == null) {
-                            animator = createAnimatorFromXml(context, parser);
+                            animator = createAnimatorFromXml(context.getResources(),
+                                    context.getTheme(), parser);
                         }
 
                         if (animator == null) {
@@ -166,103 +186,8 @@
         }
     }
 
-    private static Animator createAnimatorFromXml(Context c, XmlPullParser parser)
-            throws XmlPullParserException, IOException {
-        return createAnimatorFromXml(c, parser, Xml.asAttributeSet(parser), null, 0);
-    }
 
-    private static Animator createAnimatorFromXml(Context c, XmlPullParser parser,
-            AttributeSet attrs, AnimatorSet parent, int sequenceOrdering)
-            throws XmlPullParserException, IOException {
-
-        Animator anim = null;
-        ArrayList<Animator> childAnims = null;
-
-        // Make sure we are on a start tag.
-        int type;
-        int depth = parser.getDepth();
-
-        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
-               && type != XmlPullParser.END_DOCUMENT) {
-
-            if (type != XmlPullParser.START_TAG) {
-                continue;
-            }
-
-            String  name = parser.getName();
-
-            if (name.equals("objectAnimator")) {
-                anim = loadObjectAnimator(c, attrs);
-            } else if (name.equals("animator")) {
-                anim = loadAnimator(c, attrs, null);
-            } else if (name.equals("set")) {
-                anim = new AnimatorSet();
-                TypedArray a = c.obtainStyledAttributes(attrs,
-                        com.android.internal.R.styleable.AnimatorSet);
-                int ordering = a.getInt(com.android.internal.R.styleable.AnimatorSet_ordering,
-                        TOGETHER);
-                createAnimatorFromXml(c, parser, attrs, (AnimatorSet) anim,  ordering);
-                a.recycle();
-            } else {
-                throw new RuntimeException("Unknown animator name: " + parser.getName());
-            }
-
-            if (parent != null) {
-                if (childAnims == null) {
-                    childAnims = new ArrayList<Animator>();
-                }
-                childAnims.add(anim);
-            }
-        }
-        if (parent != null && childAnims != null) {
-            Animator[] animsArray = new Animator[childAnims.size()];
-            int index = 0;
-            for (Animator a : childAnims) {
-                animsArray[index++] = a;
-            }
-            if (sequenceOrdering == TOGETHER) {
-                parent.playTogether(animsArray);
-            } else {
-                parent.playSequentially(animsArray);
-            }
-        }
-
-        return anim;
-
-    }
-
-    private static ObjectAnimator loadObjectAnimator(Context context, AttributeSet attrs)
-            throws NotFoundException {
-
-        ObjectAnimator anim = new ObjectAnimator();
-
-        loadAnimator(context, attrs, anim);
-
-        TypedArray a =
-                context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.PropertyAnimator);
-
-        String propertyName = a.getString(com.android.internal.R.styleable.PropertyAnimator_propertyName);
-
-        anim.setPropertyName(propertyName);
-
-        a.recycle();
-
-        return anim;
-    }
-
-    /**
-     * Creates a new animation whose parameters come from the specified context and
-     * attributes set.
-     *
-     * @param context the application environment
-     * @param attrs the set of attributes holding the animation parameters
-     */
-    private static ValueAnimator loadAnimator(Context context, AttributeSet attrs, ValueAnimator anim)
-            throws NotFoundException {
-
-        TypedArray a =
-                context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animator);
-
+    private static void parseAnimatorFromTypeArray(ValueAnimator anim, TypedArray a) {
         long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 300);
 
         long startDelay = a.getInt(com.android.internal.R.styleable.Animator_startOffset, 0);
@@ -378,11 +303,123 @@
         if (evaluator != null) {
             anim.setEvaluator(evaluator);
         }
+    }
+
+    private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        return createAnimatorFromXml(res, theme, parser, Xml.asAttributeSet(parser), null, 0);
+    }
+
+    private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser,
+            AttributeSet attrs, AnimatorSet parent, int sequenceOrdering)
+            throws XmlPullParserException, IOException {
+
+        Animator anim = null;
+        ArrayList<Animator> childAnims = null;
+
+        // Make sure we are on a start tag.
+        int type;
+        int depth = parser.getDepth();
+
+        while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+                && type != XmlPullParser.END_DOCUMENT) {
+
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            String name = parser.getName();
+
+            if (name.equals("objectAnimator")) {
+                anim = loadObjectAnimator(res, theme, attrs);
+            } else if (name.equals("animator")) {
+                anim = loadAnimator(res, theme, attrs, null);
+            } else if (name.equals("set")) {
+                anim = new AnimatorSet();
+                TypedArray a;
+                if (theme != null) {
+                    a = theme.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AnimatorSet, 0, 0);
+                } else {
+                    a = res.obtainAttributes(attrs, com.android.internal.R.styleable.AnimatorSet);
+                }
+                int ordering = a.getInt(com.android.internal.R.styleable.AnimatorSet_ordering,
+                        TOGETHER);
+                createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering);
+                a.recycle();
+            } else {
+                throw new RuntimeException("Unknown animator name: " + parser.getName());
+            }
+
+            if (parent != null) {
+                if (childAnims == null) {
+                    childAnims = new ArrayList<Animator>();
+                }
+                childAnims.add(anim);
+            }
+        }
+        if (parent != null && childAnims != null) {
+            Animator[] animsArray = new Animator[childAnims.size()];
+            int index = 0;
+            for (Animator a : childAnims) {
+                animsArray[index++] = a;
+            }
+            if (sequenceOrdering == TOGETHER) {
+                parent.playTogether(animsArray);
+            } else {
+                parent.playSequentially(animsArray);
+            }
+        }
+
+        return anim;
+
+    }
+
+    private static ObjectAnimator loadObjectAnimator(Resources res, Theme theme, AttributeSet attrs)
+            throws NotFoundException {
+        ObjectAnimator anim = new ObjectAnimator();
+
+        loadAnimator(res, theme, attrs, anim);
+
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, R.styleable.PropertyAnimator, 0, 0);
+        } else {
+            a = res.obtainAttributes(attrs, R.styleable.PropertyAnimator);
+        }
+
+        String propertyName = a.getString(R.styleable.PropertyAnimator_propertyName);
+
+        anim.setPropertyName(propertyName);
+
+        a.recycle();
+
+        return anim;
+    }
+
+    /**
+     * Creates a new animation whose parameters come from the specified context
+     * and attributes set.
+     *
+     * @param res The resources
+     * @param attrs The set of attributes holding the animation parameters
+     */
+    private static ValueAnimator loadAnimator(Resources res, Theme theme,
+            AttributeSet attrs, ValueAnimator anim)
+            throws NotFoundException {
+
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, R.styleable.Animator, 0, 0);
+        } else {
+            a = res.obtainAttributes(attrs, R.styleable.Animator);
+        }
+
+        parseAnimatorFromTypeArray(anim, a);
 
         final int resID =
                 a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
         if (resID > 0) {
-            anim.setInterpolator(AnimationUtils.loadInterpolator(context, resID));
+            anim.setInterpolator(AnimationUtils.loadInterpolator(res, theme, resID));
         }
         a.recycle();
 
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 0f65454..56462ae 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -943,7 +943,9 @@
             b = data.readStrongBinder();
             IUiAutomationConnection c = IUiAutomationConnection.Stub.asInterface(b);
             int userId = data.readInt();
-            boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId);
+            String abiOverride = data.readString();
+            boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId,
+                    abiOverride);
             reply.writeNoException();
             reply.writeInt(res ? 1 : 0);
             return true;
@@ -3339,7 +3341,8 @@
 
     public boolean startInstrumentation(ComponentName className, String profileFile,
             int flags, Bundle arguments, IInstrumentationWatcher watcher,
-            IUiAutomationConnection connection, int userId) throws RemoteException {
+            IUiAutomationConnection connection, int userId, String instructionSet)
+            throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
@@ -3350,6 +3353,7 @@
         data.writeStrongBinder(watcher != null ? watcher.asBinder() : null);
         data.writeStrongBinder(connection != null ? connection.asBinder() : null);
         data.writeInt(userId);
+        data.writeString(instructionSet);
         mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0);
         reply.readException();
         boolean res = reply.readInt() != 0;
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index a057c3e..9160452 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -394,8 +394,18 @@
         if (sharedElements != null) {
             for (int i = 0; i < sharedElements.length; i++) {
                 Pair<View, String> sharedElement = sharedElements[i];
-                names.add(sharedElement.second);
-                mappedNames.add(sharedElement.first.getViewName());
+                String sharedElementName = sharedElement.second;
+                if (sharedElementName == null) {
+                    throw new IllegalArgumentException("Shared element name must not be null");
+                }
+                String viewName = sharedElement.first.getViewName();
+                if (viewName == null) {
+                    throw new IllegalArgumentException("Shared elements must have non-null " +
+                            "viewNames");
+                }
+
+                names.add(sharedElementName);
+                mappedNames.add(viewName);
             }
         }
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d9adba3..ea46044 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -98,6 +98,7 @@
 import com.android.internal.os.SamplingProfilerIntegration;
 import com.android.internal.util.FastPrintWriter;
 import com.android.org.conscrypt.OpenSSLSocketImpl;
+import com.android.org.conscrypt.TrustedCertificateStore;
 import com.google.android.collect.Lists;
 
 import dalvik.system.VMRuntime;
@@ -5049,6 +5050,10 @@
 
         Security.addProvider(new AndroidKeyStoreProvider());
 
+        // Make sure TrustedCertificateStore looks in the right place for CA certificates
+        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
+        TrustedCertificateStore.setDefaultUserDirectory(configDir);
+
         Process.setArgV0("<pre-initialized>");
 
         Looper.prepareMainLooper();
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index 703df51..0cccedc 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -129,9 +129,6 @@
     protected static final String KEY_SCALE_TYPE = "shared_element:scaleType";
     protected static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix";
 
-    // The background fade in/out duration. TODO: Enable tuning this.
-    public static final int FADE_BACKGROUND_DURATION_MS = 300;
-
     protected static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values();
 
     /**
@@ -261,13 +258,8 @@
         if (view == null) {
             mEpicenterCallback.setEpicenter(null);
         } else {
-            int[] loc = new int[2];
-            view.getLocationOnScreen(loc);
-            int left = loc[0] + Math.round(view.getTranslationX());
-            int top = loc[1] + Math.round(view.getTranslationY());
-            int right = left + view.getWidth();
-            int bottom = top + view.getHeight();
-            Rect epicenter = new Rect(left, top, right, bottom);
+            Rect epicenter = new Rect();
+            view.getBoundsOnScreen(epicenter);
             mEpicenterCallback.setEpicenter(epicenter);
         }
     }
@@ -352,6 +344,10 @@
                 String name = mAllSharedElementNames.get(i);
                 View sharedElement = sharedElements.get(name);
                 if (sharedElement != null) {
+                    if (sharedElement.getViewName() == null) {
+                        throw new IllegalArgumentException("Shared elements must have " +
+                                "non-null viewNames");
+                    }
                     mSharedElementNames.add(name);
                     mSharedElements.add(sharedElement);
                 }
@@ -504,15 +500,19 @@
 
     protected Bundle captureSharedElementState() {
         Bundle bundle = new Bundle();
-        int[] tempLoc = new int[2];
+        Rect tempBounds = new Rect();
         for (int i = 0; i < mSharedElementNames.size(); i++) {
             View sharedElement = mSharedElements.get(i);
             String name = mSharedElementNames.get(i);
-            captureSharedElementState(sharedElement, name, bundle, tempLoc);
+            captureSharedElementState(sharedElement, name, bundle, tempBounds);
         }
         return bundle;
     }
 
+    protected long getFadeDuration() {
+        return getWindow().getTransitionBackgroundFadeDuration();
+    }
+
     /**
      * Captures placement information for Views with a shared element name for
      * Activity Transitions.
@@ -521,20 +521,19 @@
      * @param name           The shared element name in the target Activity to apply the placement
      *                       information for.
      * @param transitionArgs Bundle to store shared element placement information.
-     * @param tempLoc        A temporary int[2] for capturing the current location of views.
+     * @param tempBounds     A temporary Rect for capturing the current location of views.
      */
     private static void captureSharedElementState(View view, String name, Bundle transitionArgs,
-            int[] tempLoc) {
+            Rect tempBounds) {
         Bundle sharedElementBundle = new Bundle();
-        view.getLocationOnScreen(tempLoc);
-        float scaleX = view.getScaleX();
-        sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]);
-        int width = Math.round(view.getWidth() * scaleX);
+        tempBounds.set(0, 0, view.getWidth(), view.getHeight());
+        view.getBoundsOnScreen(tempBounds);
+        sharedElementBundle.putInt(KEY_SCREEN_X, tempBounds.left);
+        int width = tempBounds.width();
         sharedElementBundle.putInt(KEY_WIDTH, width);
 
-        float scaleY = view.getScaleY();
-        sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]);
-        int height = Math.round(view.getHeight() * scaleY);
+        sharedElementBundle.putInt(KEY_SCREEN_Y, tempBounds.top);
+        int height = tempBounds.height();
         sharedElementBundle.putInt(KEY_HEIGHT, height);
 
         sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 097c64e..94ea2c5 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -53,6 +53,7 @@
     private int mHeight;
     private Surface mSurface;
     private int mLastVisibility;
+    private ActivityViewCallback mActivityViewCallback;
 
     // Only one IIntentSender or Intent may be queued at a time. Most recent one wins.
     IIntentSender mQueuedPendingIntent;
@@ -254,6 +255,25 @@
         }
     }
 
+    /**
+     * Set the callback to use to report certain state changes.
+     * @param callback The callback to report events to.
+     *
+     * @see ActivityViewCallback
+     */
+    public void setCallback(ActivityViewCallback callback) {
+        mActivityViewCallback = callback;
+    }
+
+    public static abstract class ActivityViewCallback {
+        /**
+         * Called when all activities in the ActivityView have completed and been removed. Register
+         * using {@link ActivityView#setCallback(ActivityViewCallback)}. Each ActivityView may
+         * have at most one callback registered.
+         */
+        public abstract void onAllActivitiesComplete(ActivityView view);
+    }
+
     private class ActivityViewSurfaceTextureListener implements SurfaceTextureListener {
         @Override
         public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width,
@@ -313,14 +333,32 @@
             if (DEBUG) Log.v(TAG, "setVisible(): container=" + container + " visible=" + visible +
                     " ActivityView=" + mActivityViewWeakReference.get());
         }
+
+        @Override
+        public void onAllActivitiesComplete(IBinder container) {
+            final ActivityView activityView = mActivityViewWeakReference.get();
+            if (activityView != null) {
+                final ActivityViewCallback callback = activityView.mActivityViewCallback;
+                if (callback != null) {
+                    activityView.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            callback.onAllActivitiesComplete(activityView);
+                        }
+                    });
+                }
+            }
+        }
     }
 
     private static class ActivityContainerWrapper {
         private final IActivityContainer mIActivityContainer;
         private final CloseGuard mGuard = CloseGuard.get();
+        boolean mOpened; // Protected by mGuard.
 
         ActivityContainerWrapper(IActivityContainer container) {
             mIActivityContainer = container;
+            mOpened = true;
             mGuard.open("release");
         }
 
@@ -388,11 +426,16 @@
         }
 
         void release() {
-            if (DEBUG) Log.v(TAG, "ActivityContainerWrapper: release called");
-            try {
-                mIActivityContainer.release();
-                mGuard.close();
-            } catch (RemoteException e) {
+            synchronized (mGuard) {
+                if (mOpened) {
+                    if (DEBUG) Log.v(TAG, "ActivityContainerWrapper: release called");
+                    try {
+                        mIActivityContainer.release();
+                        mGuard.close();
+                    } catch (RemoteException e) {
+                    }
+                    mOpened = false;
+                }
             }
         }
 
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 2f35160..84673d9 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1455,10 +1455,10 @@
      * @hide
      */
     @Override
-    public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int userIdOrig,
-            int userIdDest) {
+    public void addCrossProfileIntentFilter(IntentFilter filter, boolean removable,
+            int sourceUserId, int targetUserId) {
         try {
-            mPM.addForwardingIntentFilter(filter, removable, userIdOrig, userIdDest);
+            mPM.addCrossProfileIntentFilter(filter, removable, sourceUserId, targetUserId);
         } catch (RemoteException e) {
             // Should never happen!
         }
@@ -1468,14 +1468,31 @@
      * @hide
      */
     @Override
-    public void clearForwardingIntentFilters(int userIdOrig) {
+    public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int sourceUserId,
+            int targetUserId) {
+        addCrossProfileIntentFilter(filter, removable, sourceUserId, targetUserId);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void clearCrossProfileIntentFilters(int sourceUserId) {
         try {
-            mPM.clearForwardingIntentFilters(userIdOrig);
+            mPM.clearCrossProfileIntentFilters(sourceUserId);
         } catch (RemoteException e) {
             // Should never happen!
         }
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    public void clearForwardingIntentFilters(int sourceUserId) {
+        clearCrossProfileIntentFilters(sourceUserId);
+    }
+
     private final ContextImpl mContext;
     private final IPackageManager mPM;
 
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index c5190d3..52d4585 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -35,7 +35,9 @@
 import android.content.IntentFilter;
 import android.content.IIntentReceiver;
 import android.content.IntentSender;
+import android.content.IRestrictionsManager;
 import android.content.ReceiverCallNotAllowedException;
+import android.content.RestrictionsManager;
 import android.content.ServiceConnection;
 import android.content.SharedPreferences;
 import android.content.pm.ApplicationInfo;
@@ -654,6 +656,13 @@
             }
         });
 
+        registerService(RESTRICTIONS_SERVICE, new ServiceFetcher() {
+            public Object createService(ContextImpl ctx) {
+                IBinder b = ServiceManager.getService(RESTRICTIONS_SERVICE);
+                IRestrictionsManager service = IRestrictionsManager.Stub.asInterface(b);
+                return new RestrictionsManager(ctx, service);
+            }
+        });
         registerService(PRINT_SERVICE, new ServiceFetcher() {
             public Object createService(ContextImpl ctx) {
                 IBinder iBinder = ServiceManager.getService(Context.PRINT_SERVICE);
@@ -1747,7 +1756,8 @@
                 arguments.setAllowFds(false);
             }
             return ActivityManagerNative.getDefault().startInstrumentation(
-                    className, profileFile, 0, arguments, null, null, getUserId());
+                    className, profileFile, 0, arguments, null, null, getUserId(),
+                    null /* ABI override */);
         } catch (RemoteException e) {
             // System has crashed, nothing we can do.
         }
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index 779e3de..f54cb87 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -66,15 +66,16 @@
         Bundle resultReceiverBundle = new Bundle();
         resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this);
         mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle);
-        getDecor().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
-            @Override
-            public boolean onPreDraw() {
-                if (mIsReadyForTransition) {
-                    getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
-                }
-                return mIsReadyForTransition;
-            }
-        });
+        getDecor().getViewTreeObserver().addOnPreDrawListener(
+                new ViewTreeObserver.OnPreDrawListener() {
+                    @Override
+                    public boolean onPreDraw() {
+                        if (mIsReadyForTransition) {
+                            getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
+                        }
+                        return mIsReadyForTransition;
+                    }
+                });
     }
 
     public void viewsReady(ArrayList<String> accepted, ArrayList<String> localNames) {
@@ -315,7 +316,7 @@
             if (background != null) {
                 background = background.mutate();
                 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 255);
-                mBackgroundAnimator.setDuration(FADE_BACKGROUND_DURATION_MS);
+                mBackgroundAnimator.setDuration(getFadeDuration());
                 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index ba1638ff..8d5b8317 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -199,7 +199,7 @@
                     }
                 }
             });
-            mBackgroundAnimator.setDuration(FADE_BACKGROUND_DURATION_MS);
+            mBackgroundAnimator.setDuration(getFadeDuration());
             mBackgroundAnimator.start();
         }
     }
diff --git a/core/java/android/app/IActivityContainerCallback.aidl b/core/java/android/app/IActivityContainerCallback.aidl
index 7f6d2c3..99d0a6f 100644
--- a/core/java/android/app/IActivityContainerCallback.aidl
+++ b/core/java/android/app/IActivityContainerCallback.aidl
@@ -21,4 +21,5 @@
 /** @hide */
 interface IActivityContainerCallback {
     oneway void setVisible(IBinder container, boolean visible);
+    oneway void onAllActivitiesComplete(IBinder container);
 }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 8434c2a..bf2d7e5 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -176,7 +176,8 @@
 
     public boolean startInstrumentation(ComponentName className, String profileFile,
             int flags, Bundle arguments, IInstrumentationWatcher watcher,
-            IUiAutomationConnection connection, int userId) throws RemoteException;
+            IUiAutomationConnection connection, int userId,
+            String abiOverride) throws RemoteException;
     public void finishInstrumentation(IApplicationThread target,
             int resultCode, Bundle results) throws RemoteException;
 
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index 1015514..45a2625 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -222,6 +222,12 @@
      * Called after the administrator is first enabled, as a result of
      * receiving {@link #ACTION_DEVICE_ADMIN_ENABLED}.  At this point you
      * can use {@link DevicePolicyManager} to set your desired policies.
+     *
+     * <p> If the admin is activated by a device owner, then the intent
+     * may contain private extras that are relevant to user setup.
+     * {@see DevicePolicyManager#createAndInitializeUser(ComponentName, String, String,
+     *      ComponentName, Intent)}
+     *
      * @param context The running context as per {@link #onReceive}.
      * @param intent The received intent as per {@link #onReceive}.
      */
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 157b8ec..e80c761 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -25,6 +25,9 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.RestrictionsManager;
+import android.media.AudioService;
+import android.net.ProxyInfo;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Process;
@@ -34,6 +37,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.service.trust.TrustAgentService;
 import android.util.Log;
 
 import com.android.org.conscrypt.TrustedCertificateStore;
@@ -81,6 +85,20 @@
     }
 
     /**
+     * Activity action: Used to indicate that the receiving activity is being started as part of the
+     * managed profile provisioning flow. This intent is typically sent to a mobile device
+     * management application (mdm) after the first part of the provisioning process is complete in
+     * the expectation that this app will (after optionally showing it's own UI) ultimately call
+     * {@link #ACTION_PROVISION_MANAGED_PROFILE} to complete the creation of the managed profile.
+     *
+     * <p> The intent may contain the extras {@link #EXTRA_PROVISIONING_TOKEN} and
+     * {@link #EXTRA_PROVISIONING_EMAIL_ADDRESS}.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_SEND_PROVISIONING_VALUES
+        = "android.app.action.ACTION_SEND_PROVISIONING_VALUES";
+
+    /**
      * Activity action: Starts the provisioning flow which sets up a managed profile.
      *
      * <p>A managed profile allows data separation for example for the usage of a
@@ -110,6 +128,17 @@
         = "android.app.action.ACTION_PROVISION_MANAGED_PROFILE";
 
     /**
+     * A broadcast intent with this action can be sent to ManagedProvisionning to specify that the
+     * user has already consented to the creation of the managed profile.
+     * The intent must contain the extras
+     * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} and
+     * {@link #EXTRA_PROVISIONING_TOKEN}
+     * @hide
+     */
+    public static final String ACTION_PROVISIONING_USER_HAS_CONSENTED
+        = "android.app.action.ACTION_PROVISIONING_USER_HAS_CONSENTED";
+
+    /**
      * A String extra holding the name of the package of the mobile device management application
      * that starts the managed provisioning flow. This package will be set as the profile owner.
      * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}.
@@ -118,6 +147,18 @@
         = "android.app.extra.deviceAdminPackageName";
 
     /**
+     * An int extra used to identify that during the current setup process the user has already
+     * consented to setting up a managed profile. This is typically received by
+     * a mobile device management application when it is started with
+     * {@link #ACTION_SEND_PROVISIONING_VALUES} and passed on in an intent
+     * {@link #ACTION_PROVISION_MANAGED_PROFILE} which starts the setup of the managed profile. The
+     * token indicates that steps asking for user consent can be skipped as the user has previously
+     * consented.
+     */
+    public static final String EXTRA_PROVISIONING_TOKEN
+        = "android.app.extra.token";
+
+    /**
      * A String extra holding the default name of the profile that is created during managed profile
      * provisioning.
      * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}
@@ -126,6 +167,17 @@
         = "android.app.extra.defaultManagedProfileName";
 
     /**
+     * A String extra holding the email address of the profile that is created during managed
+     * profile provisioning. This is typically received by a mobile management application when it
+     * is started with {@link #ACTION_SEND_PROVISIONING_VALUES} and passed on in an intent
+     * {@link #ACTION_PROVISION_MANAGED_PROFILE} which starts the setup of the managed profile. It
+     * is eventually passed on in an intent
+     * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE}.
+     */
+    public static final String EXTRA_PROVISIONING_EMAIL_ADDRESS
+        = "android.app.extra.ManagedProfileEmailAddress";
+
+    /**
      * Activity action: ask the user to add a new device administrator to the system.
      * The desired policy is the ComponentName of the policy in the
      * {@link #EXTRA_DEVICE_ADMIN} extra field.  This will invoke a UI to
@@ -183,15 +235,16 @@
     public static final String ACTION_SET_NEW_PASSWORD
             = "android.app.action.SET_NEW_PASSWORD";
     /**
-     * Flag for {@link #addForwardingIntentFilter}: the intents will forwarded to the primary user.
+     * Flag used by {@link #addCrossProfileIntentFilter} to allow access of certain intents from a
+     * managed profile to its parent.
      */
-    public static int FLAG_TO_PRIMARY_USER = 0x0001;
+    public static int FLAG_PARENT_CAN_ACCESS_MANAGED = 0x0001;
 
     /**
-     * Flag for {@link #addForwardingIntentFilter}: the intents will be forwarded to the managed
-     * profile.
+     * Flag used by {@link #addCrossProfileIntentFilter} to allow access of certain intents from the
+     * parent to its managed profile.
      */
-    public static int FLAG_TO_MANAGED_PROFILE = 0x0002;
+    public static int FLAG_MANAGED_CAN_ACCESS_PARENT = 0x0002;
 
     /**
      * Return true if the given administrator component is currently
@@ -1236,6 +1289,32 @@
     }
 
     /**
+     * Set a network-independent global HTTP proxy.  This is not normally what you want
+     * for typical HTTP proxies - they are generally network dependent.  However if you're
+     * doing something unusual like general internal filtering this may be useful.  On
+     * a private network where the proxy is not accessible, you may break HTTP using this.
+     *
+     * <p>This method requires the caller to be the device owner.
+     *
+     * <p>This proxy is only a recommendation and it is possible that some apps will ignore it.
+     * @see ProxyInfo
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated
+     *            with.
+     * @param proxyInfo The a {@link ProxyInfo} object defining the new global
+     *        HTTP proxy.  A {@code null} value will clear the global HTTP proxy.
+     */
+    public void setRecommendedGlobalProxy(ComponentName admin, ProxyInfo proxyInfo) {
+        if (mService != null) {
+            try {
+                mService.setRecommendedGlobalProxy(admin, proxyInfo);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
+
+    /**
      * Returns the component name setting the global proxy.
      * @return ComponentName object of the device admin that set the global proxy, or
      *            null if no admin has set the proxy.
@@ -1317,7 +1396,7 @@
     public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 1 << 3;
 
     /**
-     * Ignore trust agent state on secure keyguard screens
+     * Ignore {@link TrustAgentService} state on secure keyguard screens
      * (e.g. PIN/Pattern/Password).
      */
     public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 1 << 4;
@@ -1754,6 +1833,23 @@
         return isDeviceOwnerApp(packageName);
     }
 
+    /**
+     * Clears the current device owner.  The caller must be the device owner.
+     *
+     * This function should be used cautiously as once it is called it cannot
+     * be undone.  The device owner can only be set as a part of device setup
+     * before setup completes.
+     */
+    public void clearDeviceOwnerApp() {
+        if (mService != null) {
+            try {
+                mService.clearDeviceOwner(mContext.getPackageName());
+            } catch (RemoteException re) {
+                Log.w(TAG, "Failed to clear device owner");
+            }
+        }
+    }
+
     /** @hide */
     public String getDeviceOwner() {
         if (mService != null) {
@@ -1961,17 +2057,18 @@
     }
 
     /**
-     * Called by a profile owner to forward intents sent from the managed profile to the owner, or
-     * from the owner to the managed profile.
-     * If an intent matches this intent filter, then activities belonging to the other user can
-     * respond to this intent.
+     * Called by the profile owner so that some intents sent in the managed profile can also be
+     * resolved in the parent, or vice versa.
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
-     * @param filter if an intent matches this IntentFilter, then it can be forwarded.
+     * @param filter The {@link IntentFilter} the intent has to match to be also resolved in the
+     * other profile
+     * @param flags {@link DevicePolicyManager#FLAG_MANAGED_CAN_ACCESS_PARENT} and
+     * {@link DevicePolicyManager#FLAG_PARENT_CAN_ACCESS_MANAGED} are supported.
      */
-    public void addForwardingIntentFilter(ComponentName admin, IntentFilter filter, int flags) {
+    public void addCrossProfileIntentFilter(ComponentName admin, IntentFilter filter, int flags) {
         if (mService != null) {
             try {
-                mService.addForwardingIntentFilter(admin, filter, flags);
+                mService.addCrossProfileIntentFilter(admin, filter, flags);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed talking with device policy service", e);
             }
@@ -1979,14 +2076,14 @@
     }
 
     /**
-     * Called by a profile owner to remove the forwarding intent filters from the current user
-     * and from the owner.
+     * Called by a profile owner to remove the cross-profile intent filters from the managed profile
+     * and from the parent.
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      */
-    public void clearForwardingIntentFilters(ComponentName admin) {
+    public void clearCrossProfileIntentFilters(ComponentName admin) {
         if (mService != null) {
             try {
-                mService.clearForwardingIntentFilters(admin);
+                mService.clearCrossProfileIntentFilters(admin);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed talking with device policy service", e);
             }
@@ -2014,6 +2111,41 @@
     }
 
     /**
+     * Called by a device owner to create a user with the specified name. The UserHandle returned
+     * by this method should not be persisted as user handles are recycled as users are removed and
+     * created. If you need to persist an identifier for this user, use
+     * {@link UserManager#getSerialNumberForUser}.  The new user will be started in the background
+     * immediately.
+     *
+     * <p> profileOwnerComponent is the {@link DeviceAdminReceiver} to be the profile owner as well
+     * as registered as an active admin on the new user.  The profile owner package will be
+     * installed on the new user if it already is installed on the device.
+     *
+     * <p>If the optionalInitializeData is not null, then the extras will be passed to the
+     * profileOwnerComponent when onEnable is called.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param name the user's name
+     * @param ownerName the human readable name of the organisation associated with this DPM.
+     * @param profileOwnerComponent The {@link DeviceAdminReceiver} that will be an active admin on
+     *      the user.
+     * @param adminExtras Extras that will be passed to onEnable of the admin receiver
+     *      on the new user.
+     * @see UserHandle
+     * @return the UserHandle object for the created user, or null if the user could not be created.
+     */
+    public UserHandle createAndInitializeUser(ComponentName admin, String name, String ownerName,
+            ComponentName profileOwnerComponent, Bundle adminExtras) {
+        try {
+            return mService.createAndInitializeUser(admin, name, ownerName, profileOwnerComponent,
+                    adminExtras);
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not create a user", re);
+        }
+        return null;
+    }
+
+    /**
      * Called by a device owner to remove a user and all associated data. The primary user can
      * not be removed.
      *
@@ -2161,45 +2293,6 @@
     }
 
     /**
-     * Called by profile or device owner to re-enable a system app that was disabled by default
-     * when the managed profile was created. This should only be called from a profile or device
-     * owner running within a managed profile.
-     *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
-     * @param packageName The package to be re-enabled in the current profile.
-     */
-    public void enableSystemApp(ComponentName admin, String packageName) {
-        if (mService != null) {
-            try {
-                mService.enableSystemApp(admin, packageName);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to install package: " + packageName);
-            }
-        }
-    }
-
-    /**
-     * Called by profile or device owner to re-enable system apps by intent that were disabled
-     * by default when the managed profile was created. This should only be called from a profile
-     * or device owner running within a managed profile.
-     *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
-     * @param intent An intent matching the app(s) to be installed. All apps that resolve for this
-     *               intent will be re-enabled in the current profile.
-     * @return int The number of activities that matched the intent and were installed.
-     */
-    public int enableSystemApp(ComponentName admin, Intent intent) {
-        if (mService != null) {
-            try {
-                return mService.enableSystemAppWithIntent(admin, intent);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to install packages matching filter: " + intent);
-            }
-        }
-        return 0;
-    }
-
-    /**
      * Called by a profile owner to disable account management for a specific type of account.
      *
      * <p>The calling device admin must be a profile owner. If it is not, a
@@ -2330,4 +2423,56 @@
         }
     }
 
+    /**
+     * Designates a specific broadcast receiver component as the provider for
+     * making permission requests of a local or remote administrator of the user.
+     * <p/>
+     * Only a profile owner can designate the restrictions provider.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param receiver The component name of a BroadcastReceiver that handles the
+     * {@link RestrictionsManager#ACTION_REQUEST_PERMISSION} intent. If this param is null,
+     * it removes the restrictions provider previously assigned.
+     */
+    public void setRestrictionsProvider(ComponentName admin, ComponentName receiver) {
+        if (mService != null) {
+            try {
+                mService.setRestrictionsProvider(admin, receiver);
+            } catch (RemoteException re) {
+                Log.w(TAG, "Failed to set permission provider on device policy service");
+            }
+        }
+    }
+
+    /**
+     * Called by profile or device owners to set the master volume mute on or off.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param on {@code true} to mute master volume, {@code false} to turn mute off.
+     */
+    public void setMasterVolumeMuted(ComponentName admin, boolean on) {
+        if (mService != null) {
+            try {
+                mService.setMasterVolumeMuted(admin, on);
+            } catch (RemoteException re) {
+                Log.w(TAG, "Failed to setMasterMute on device policy service");
+            }
+        }
+    }
+
+    /**
+     * Called by profile or device owners to check whether the master volume mute is on or off.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @return {@code true} if master volume is muted, {@code false} if it's not.
+     */
+    public boolean isMasterVolumeMuted(ComponentName admin) {
+        if (mService != null) {
+            try {
+                return mService.isMasterVolumeMuted(admin);
+            } catch (RemoteException re) {
+                Log.w(TAG, "Failed to get isMasterMute on device policy service");
+            }
+        }
+        return false;
+    }
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3c08c14..a1caa21 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -20,6 +20,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.ProxyInfo;
 import android.os.Bundle;
 import android.os.RemoteCallback;
 import android.os.UserHandle;
@@ -78,6 +79,7 @@
 
     ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList, int userHandle);
     ComponentName getGlobalProxyAdmin(int userHandle);
+    void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo);
 
     int setStorageEncryption(in ComponentName who, boolean encrypt, int userHandle);
     boolean getStorageEncryption(in ComponentName who, int userHandle);
@@ -106,6 +108,7 @@
     boolean isDeviceOwner(String packageName);
     String getDeviceOwner();
     String getDeviceOwnerName();
+    void clearDeviceOwner(String packageName);
 
     boolean setProfileOwner(String packageName, String ownerName, int userHandle);
     String getProfileOwner(int userHandle);
@@ -121,20 +124,21 @@
     void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings);
     Bundle getApplicationRestrictions(in ComponentName who, in String packageName);
 
+    void setRestrictionsProvider(in ComponentName who, in ComponentName provider);
+    ComponentName getRestrictionsProvider(int userHandle);
+
     void setUserRestriction(in ComponentName who, in String key, boolean enable);
-    void addForwardingIntentFilter(in ComponentName admin, in IntentFilter filter, int flags);
-    void clearForwardingIntentFilters(in ComponentName admin);
+    void addCrossProfileIntentFilter(in ComponentName admin, in IntentFilter filter, int flags);
+    void clearCrossProfileIntentFilters(in ComponentName admin);
 
     boolean setApplicationBlocked(in ComponentName admin, in String packageName, boolean blocked);
     int setApplicationsBlocked(in ComponentName admin, in Intent intent, boolean blocked);
     boolean isApplicationBlocked(in ComponentName admin, in String packageName);
 
     UserHandle createUser(in ComponentName who, in String name);
+    UserHandle createAndInitializeUser(in ComponentName who, in String name, in String profileOwnerName, in ComponentName profileOwnerComponent, in Bundle adminExtras);
     boolean removeUser(in ComponentName who, in UserHandle userHandle);
 
-    void enableSystemApp(in ComponentName admin, in String packageName);
-    int enableSystemAppWithIntent(in ComponentName admin, in Intent intent);
-
     void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled);
     String[] getAccountTypesWithManagementDisabled();
 
@@ -144,4 +148,7 @@
 
     void setGlobalSetting(in ComponentName who, in String setting, in String value);
     void setSecureSetting(in ComponentName who, in String setting, in String value);
+
+    void setMasterVolumeMuted(in ComponentName admin, boolean on);
+    boolean isMasterVolumeMuted(in ComponentName admin);
 }
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
new file mode 100644
index 0000000..46f082e
--- /dev/null
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import com.android.internal.backup.IBackupTransport;
+
+/**
+ * Concrete class that provides a stable-API bridge between IBackupTransport
+ * and its implementations.
+ *
+ * @hide
+ */
+public class BackupTransport {
+    public static final int TRANSPORT_OK = 0;
+    public static final int TRANSPORT_ERROR = 1;
+    public static final int TRANSPORT_NOT_INITIALIZED = 2;
+    public static final int TRANSPORT_PACKAGE_REJECTED = 3;
+    public static final int AGENT_ERROR = 4;
+    public static final int AGENT_UNKNOWN = 5;
+
+    IBackupTransport mBinderImpl = new TransportImpl();
+    /** @hide */
+    public IBinder getBinder() {
+        return mBinderImpl.asBinder();
+    }
+
+    // ------------------------------------------------------------------------------------
+    // Transport self-description and general configuration interfaces
+    //
+
+    /**
+     * Ask the transport for the name under which it should be registered.  This will
+     * typically be its host service's component name, but need not be.
+     */
+    public String name() {
+        throw new UnsupportedOperationException("Transport name() not implemented");
+    }
+
+    /**
+     * Ask the transport for an Intent that can be used to launch any internal
+     * configuration Activity that it wishes to present.  For example, the transport
+     * may offer a UI for allowing the user to supply login credentials for the
+     * transport's off-device backend.
+     *
+     * If the transport does not supply any user-facing configuration UI, it should
+     * return null from this method.
+     *
+     * @return An Intent that can be passed to Context.startActivity() in order to
+     *         launch the transport's configuration UI.  This method will return null
+     *         if the transport does not offer any user-facing configuration UI.
+     */
+    public Intent configurationIntent() {
+        return null;
+    }
+
+    /**
+     * On demand, supply a one-line string that can be shown to the user that
+     * describes the current backend destination.  For example, a transport that
+     * can potentially associate backup data with arbitrary user accounts should
+     * include the name of the currently-active account here.
+     *
+     * @return A string describing the destination to which the transport is currently
+     *         sending data.  This method should not return null.
+     */
+    public String currentDestinationString() {
+        throw new UnsupportedOperationException(
+                "Transport currentDestinationString() not implemented");
+    }
+
+    /**
+     * Ask the transport where, on local device storage, to keep backup state blobs.
+     * This is per-transport so that mock transports used for testing can coexist with
+     * "live" backup services without interfering with the live bookkeeping.  The
+     * returned string should be a name that is expected to be unambiguous among all
+     * available backup transports; the name of the class implementing the transport
+     * is a good choice.
+     *
+     * @return A unique name, suitable for use as a file or directory name, that the
+     *         Backup Manager could use to disambiguate state files associated with
+     *         different backup transports.
+     */
+    public String transportDirName() {
+        throw new UnsupportedOperationException(
+                "Transport transportDirName() not implemented");
+    }
+
+    // ------------------------------------------------------------------------------------
+    // Device-level operations common to both key/value and full-data storage
+
+    /**
+     * Initialize the server side storage for this device, erasing all stored data.
+     * The transport may send the request immediately, or may buffer it.  After
+     * this is called, {@link #finishBackup} will be called to ensure the request
+     * is sent and received successfully.
+     *
+     * @return One of {@link BackupTransport#TRANSPORT_OK} (OK so far) or
+     *   {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure).
+     */
+    public int initializeDevice() {
+        return BackupTransport.TRANSPORT_ERROR;
+    }
+
+    /**
+     * Erase the given application's data from the backup destination.  This clears
+     * out the given package's data from the current backup set, making it as though
+     * the app had never yet been backed up.  After this is called, {@link finishBackup}
+     * must be called to ensure that the operation is recorded successfully.
+     *
+     * @return the same error codes as {@link #performBackup}.
+     */
+    public int clearBackupData(PackageInfo packageInfo) {
+        return BackupTransport.TRANSPORT_ERROR;
+    }
+
+    /**
+     * Finish sending application data to the backup destination.  This must be
+     * called after {@link #performBackup}, {@link #performFullBackup}, or {@link clearBackupData}
+     * to ensure that all data is sent and the operation properly finalized.  Only when this
+     * method returns true can a backup be assumed to have succeeded.
+     *
+     * @return the same error codes as {@link #performBackup} or {@link #performFullBackup}.
+     */
+    public int finishBackup() {
+        return BackupTransport.TRANSPORT_ERROR;
+    }
+
+    // ------------------------------------------------------------------------------------
+    // Key/value incremental backup support interfaces
+
+    /**
+     * Verify that this is a suitable time for a key/value backup pass.  This should return zero
+     * if a backup is reasonable right now, some positive value otherwise.  This method
+     * will be called outside of the {@link #performBackup}/{@link #finishBackup} pair.
+     *
+     * <p>If this is not a suitable time for a backup, the transport should return a
+     * backoff delay, in milliseconds, after which the Backup Manager should try again.
+     *
+     * @return Zero if this is a suitable time for a backup pass, or a positive time delay
+     *   in milliseconds to suggest deferring the backup pass for a while.
+     */
+    public long requestBackupTime() {
+        return 0;
+    }
+
+    /**
+     * Send one application's key/value data update to the backup destination.  The
+     * transport may send the data immediately, or may buffer it.  After this is called,
+     * {@link #finishBackup} will be called to ensure the data is sent and recorded successfully.
+     *
+     * @param packageInfo The identity of the application whose data is being backed up.
+     *   This specifically includes the signature list for the package.
+     * @param data The data stream that resulted from invoking the application's
+     *   BackupService.doBackup() method.  This may be a pipe rather than a file on
+     *   persistent media, so it may not be seekable.
+     * @param wipeAllFirst When true, <i>all</i> backed-up data for the current device/account
+     *   must be erased prior to the storage of the data provided here.  The purpose of this
+     *   is to provide a guarantee that no stale data exists in the restore set when the
+     *   device begins providing incremental backups.
+     * @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far),
+     *  {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), or
+     *  {@link BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
+     *  become lost due to inactivity purge or some other reason and needs re-initializing)
+     */
+    public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd) {
+        return BackupTransport.TRANSPORT_ERROR;
+    }
+
+    // ------------------------------------------------------------------------------------
+    // Key/value dataset restore interfaces
+
+    /**
+     * Get the set of all backups currently available over this transport.
+     *
+     * @return Descriptions of the set of restore images available for this device,
+     *   or null if an error occurred (the attempt should be rescheduled).
+     **/
+    public RestoreSet[] getAvailableRestoreSets() {
+        return null;
+    }
+
+    /**
+     * Get the identifying token of the backup set currently being stored from
+     * this device.  This is used in the case of applications wishing to restore
+     * their last-known-good data.
+     *
+     * @return A token that can be passed to {@link #startRestore}, or 0 if there
+     *   is no backup set available corresponding to the current device state.
+     */
+    public long getCurrentRestoreSet() {
+        return 0;
+    }
+
+    /**
+     * Start restoring application data from backup.  After calling this function,
+     * alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData}
+     * to walk through the actual application data.
+     *
+     * @param token A backup token as returned by {@link #getAvailableRestoreSets}
+     *   or {@link #getCurrentRestoreSet}.
+     * @param packages List of applications to restore (if data is available).
+     *   Application data will be restored in the order given.
+     * @return One of {@link BackupTransport#TRANSPORT_OK} (OK so far, call
+     *   {@link #nextRestorePackage}) or {@link BackupTransport#TRANSPORT_ERROR}
+     *   (an error occurred, the restore should be aborted and rescheduled).
+     */
+    public int startRestore(long token, PackageInfo[] packages) {
+        return BackupTransport.TRANSPORT_ERROR;
+    }
+
+    /**
+     * Get the package name of the next application with data in the backup store.
+     *
+     * @return The name of one of the packages supplied to {@link #startRestore},
+     *   or "" (the empty string) if no more backup data is available,
+     *   or null if an error occurred (the restore should be aborted and rescheduled).
+     */
+    public String nextRestorePackage() {
+        return null;
+    }
+
+    /**
+     * Get the data for the application returned by {@link #nextRestorePackage}.
+     * @param data An open, writable file into which the backup data should be stored.
+     * @return the same error codes as {@link #startRestore}.
+     */
+    public int getRestoreData(ParcelFileDescriptor outFd) {
+        return BackupTransport.TRANSPORT_ERROR;
+    }
+
+    /**
+     * End a restore session (aborting any in-process data transfer as necessary),
+     * freeing any resources and connections used during the restore process.
+     */
+    public void finishRestore() {
+        throw new UnsupportedOperationException(
+                "Transport finishRestore() not implemented");
+    }
+
+    // ------------------------------------------------------------------------------------
+    // Full backup interfaces
+
+    /**
+     * Verify that this is a suitable time for a full-data backup pass.  This should return zero
+     * if a backup is reasonable right now, some positive value otherwise.  This method
+     * will be called outside of the {@link #performFullBackup}/{@link #finishBackup} pair.
+     *
+     * <p>If this is not a suitable time for a backup, the transport should return a
+     * backoff delay, in milliseconds, after which the Backup Manager should try again.
+     *
+     * @return Zero if this is a suitable time for a backup pass, or a positive time delay
+     *   in milliseconds to suggest deferring the backup pass for a while.
+     *
+     * @see #requestBackupTime()
+     */
+    public long requestFullBackupTime() {
+        return 0;
+    }
+
+    /**
+     * Begin the process of sending an application's full-data archive to the backend.
+     * The description of the package whose data will be delivered is provided, as well as
+     * the socket file descriptor on which the transport will receive the data itself.
+     *
+     * <p>If the package is not eligible for backup, the transport should return
+     * {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED}.  In this case the system will
+     * simply proceed with the next candidate if any, or finish the full backup operation
+     * if all apps have been processed.
+     *
+     * <p>After the transport returns {@link BackupTransport#TRANSPORT_OK} from this
+     * method, the OS will proceed to call {@link #sendBackupData()} one or more times
+     * to deliver the application's data as a streamed tarball.  The transport should not
+     * read() from the socket except as instructed to via the {@link #sendBackupData(int)}
+     * method.
+     *
+     * <p>After all data has been delivered to the transport, the system will call
+     * {@link #finishBackup()}.  At this point the transport should commit the data to
+     * its datastore, if appropriate, and close the socket that had been provided in
+     * {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}.
+     *
+     * @param targetPackage The package whose data is to follow.
+     * @param socket The socket file descriptor through which the data will be provided.
+     *    If the transport returns {@link #TRANSPORT_PACKAGE_REJECTED} here, it must still
+     *    close this file descriptor now; otherwise it should be cached for use during
+     *    succeeding calls to {@link #sendBackupData(int)}, and closed in response to
+     *    {@link #finishBackup()}.
+     * @return TRANSPORT_PACKAGE_REJECTED to indicate that the stated application is not
+     *    to be backed up; TRANSPORT_OK to indicate that the OS may proceed with delivering
+     *    backup data; TRANSPORT_ERROR to indicate a fatal error condition that precludes
+     *    performing a backup at this time.
+     */
+    public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) {
+        return BackupTransport.TRANSPORT_PACKAGE_REJECTED;
+    }
+
+    /**
+     * Tells the transport to read {@code numBytes} bytes of data from the socket file
+     * descriptor provided in the {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}
+     * call, and deliver those bytes to the datastore.
+     *
+     * @param numBytes The number of bytes of tarball data available to be read from the
+     *    socket.
+     * @return TRANSPORT_OK on successful processing of the data; TRANSPORT_ERROR to
+     *    indicate a fatal error situation.  If an error is returned, the system will
+     *    call finishBackup() and stop attempting backups until after a backoff and retry
+     *    interval.
+     */
+    public int sendBackupData(int numBytes) {
+        return BackupTransport.TRANSPORT_ERROR;
+    }
+
+    /**
+     * Bridge between the actual IBackupTransport implementation and the stable API.  If the
+     * binder interface needs to change, we use this layer to translate so that we can
+     * (if appropriate) decouple those framework-side changes from the BackupTransport
+     * implementations.
+     */
+    class TransportImpl extends IBackupTransport.Stub {
+
+        @Override
+        public String name() throws RemoteException {
+            return BackupTransport.this.name();
+        }
+
+        @Override
+        public Intent configurationIntent() throws RemoteException {
+            return BackupTransport.this.configurationIntent();
+        }
+
+        @Override
+        public String currentDestinationString() throws RemoteException {
+            return BackupTransport.this.currentDestinationString();
+        }
+
+        @Override
+        public String transportDirName() throws RemoteException {
+            return BackupTransport.this.transportDirName();
+        }
+
+        @Override
+        public long requestBackupTime() throws RemoteException {
+            return BackupTransport.this.requestBackupTime();
+        }
+
+        @Override
+        public int initializeDevice() throws RemoteException {
+            return BackupTransport.this.initializeDevice();
+        }
+
+        @Override
+        public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd)
+                throws RemoteException {
+            return BackupTransport.this.performBackup(packageInfo, inFd);
+        }
+
+        @Override
+        public int clearBackupData(PackageInfo packageInfo) throws RemoteException {
+            return BackupTransport.this.clearBackupData(packageInfo);
+        }
+
+        @Override
+        public int finishBackup() throws RemoteException {
+            return BackupTransport.this.finishBackup();
+        }
+
+        @Override
+        public RestoreSet[] getAvailableRestoreSets() throws RemoteException {
+            return BackupTransport.this.getAvailableRestoreSets();
+        }
+
+        @Override
+        public long getCurrentRestoreSet() throws RemoteException {
+            return BackupTransport.this.getCurrentRestoreSet();
+        }
+
+        @Override
+        public int startRestore(long token, PackageInfo[] packages) throws RemoteException {
+            return BackupTransport.this.startRestore(token, packages);
+        }
+
+        @Override
+        public String nextRestorePackage() throws RemoteException {
+            return BackupTransport.this.nextRestorePackage();
+        }
+
+        @Override
+        public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
+            return BackupTransport.this.getRestoreData(outFd);
+        }
+
+        @Override
+        public void finishRestore() throws RemoteException {
+            BackupTransport.this.finishRestore();
+        }
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 7f8d0ab..64d80a0 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -874,6 +874,26 @@
     }
 
     /**
+     * Returns whether there is an open connection to this device.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
+     *
+     * @return True if there is at least one open connection to this device.
+     * @hide
+     */
+    public boolean isConnected() {
+        if (sService == null) {
+            // BT is not enabled, we cannot be connected.
+            return false;
+        }
+        try {
+            return sService.isConnected(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+
+    /**
      * Get the Bluetooth class of the remote device.
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
      *
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 1574090..d898060 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -104,6 +104,12 @@
     public static final int MAP = 9;
 
     /**
+     * A2DP Sink Profile
+     * @hide
+     */
+    public static final int A2DP_SINK = 10;
+
+    /**
      * Default priority for devices that we try to auto-connect to and
      * and allow incoming connections for the profile
      * @hide
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 07db8cc..a45c6b8 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -58,6 +58,7 @@
     boolean cancelBondProcess(in BluetoothDevice device);
     boolean removeBond(in BluetoothDevice device);
     int getBondState(in BluetoothDevice device);
+    boolean isConnected(in BluetoothDevice device);
 
     String getRemoteName(in BluetoothDevice device);
     int getRemoteType(in BluetoothDevice device);
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 00a0750..273d76d 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -35,8 +35,6 @@
 
     void startScan(in int appIf, in boolean isServer);
     void startScanWithUuids(in int appIf, in boolean isServer, in ParcelUuid[] ids);
-    void startScanWithUuidsScanParam(in int appIf, in boolean isServer,
-                    in ParcelUuid[] ids, int scanWindow, int scanInterval);
     void startScanWithFilters(in int appIf, in boolean isServer,
                               in ScanSettings settings, in List<ScanFilter> filters);
     void stopScan(in int appIf, in boolean isServer);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index cdcfd2e..ccf8451 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2460,7 +2460,6 @@
      *
      * @see #getSystemService
      * @see android.app.FingerprintManager
-     * @hide
      */
     public static final String FINGERPRINT_SERVICE = "fingerprint";
 
@@ -2686,6 +2685,15 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a
+     * {@link android.content.RestrictionsManager} for retrieving application restrictions
+     * and requesting permissions for restricted operations.
+     * @see #getSystemService
+     * @see android.content.RestrictionsManager
+     */
+    public static final String RESTRICTIONS_SERVICE = "restrictions";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a
      * {@link android.app.AppOpsManager} for tracking application operations
      * on the device.
      *
diff --git a/core/java/android/content/IRestrictionsManager.aidl b/core/java/android/content/IRestrictionsManager.aidl
new file mode 100644
index 0000000..b1c0a3a
--- /dev/null
+++ b/core/java/android/content/IRestrictionsManager.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.os.Bundle;
+
+/**
+ * Interface used by the RestrictionsManager
+ * @hide
+ */
+interface IRestrictionsManager {
+    Bundle getApplicationRestrictions(in String packageName);
+    boolean hasRestrictionsProvider();
+    void requestPermission(in String packageName, in String requestTemplate, in Bundle requestData);
+    void notifyPermissionResponse(in String packageName, in Bundle response);
+}
diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java
index 3ff53bf..62f88a9 100644
--- a/core/java/android/content/RestrictionEntry.java
+++ b/core/java/android/content/RestrictionEntry.java
@@ -73,32 +73,38 @@
      */
     public static final int TYPE_MULTI_SELECT = 4;
 
+    /**
+     * A type of restriction. Use this for storing an integer value. The range of values
+     * is from {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}.
+     */
+    public static final int TYPE_INTEGER = 5;
+
     /** The type of restriction. */
-    private int type;
+    private int mType;
 
     /** The unique key that identifies the restriction. */
-    private String key;
+    private String mKey;
 
     /** The user-visible title of the restriction. */
-    private String title;
+    private String mTitle;
 
     /** The user-visible secondary description of the restriction. */
-    private String description;
+    private String mDescription;
 
     /** The user-visible set of choices used for single-select and multi-select lists. */
-    private String [] choices;
+    private String [] mChoiceEntries;
 
     /** The values corresponding to the user-visible choices. The value(s) of this entry will
      * one or more of these, returned by {@link #getAllSelectedStrings()} and
      * {@link #getSelectedString()}.
      */
-    private String [] values;
+    private String [] mChoiceValues;
 
     /* The chosen value, whose content depends on the type of the restriction. */
-    private String currentValue;
+    private String mCurrentValue;
 
     /* List of selected choices in the multi-select case. */
-    private String[] currentValues;
+    private String[] mCurrentValues;
 
     /**
      * Constructor for {@link #TYPE_CHOICE} type.
@@ -106,9 +112,9 @@
      * @param selectedString the current value
      */
     public RestrictionEntry(String key, String selectedString) {
-        this.key = key;
-        this.type = TYPE_CHOICE;
-        this.currentValue = selectedString;
+        this.mKey = key;
+        this.mType = TYPE_CHOICE;
+        this.mCurrentValue = selectedString;
     }
 
     /**
@@ -117,8 +123,8 @@
      * @param selectedState whether this restriction is selected or not
      */
     public RestrictionEntry(String key, boolean selectedState) {
-        this.key = key;
-        this.type = TYPE_BOOLEAN;
+        this.mKey = key;
+        this.mType = TYPE_BOOLEAN;
         setSelectedState(selectedState);
     }
 
@@ -128,9 +134,20 @@
      * @param selectedStrings the list of values that are currently selected
      */
     public RestrictionEntry(String key, String[] selectedStrings) {
-        this.key = key;
-        this.type = TYPE_MULTI_SELECT;
-        this.currentValues = selectedStrings;
+        this.mKey = key;
+        this.mType = TYPE_MULTI_SELECT;
+        this.mCurrentValues = selectedStrings;
+    }
+
+    /**
+     * Constructor for {@link #TYPE_INTEGER} type.
+     * @param key the unique key for this restriction
+     * @param selectedInt the integer value of the restriction
+     */
+    public RestrictionEntry(String key, int selectedInt) {
+        mKey = key;
+        mType = TYPE_INTEGER;
+        setIntValue(selectedInt);
     }
 
     /**
@@ -138,7 +155,7 @@
      * @param type the type for this restriction.
      */
     public void setType(int type) {
-        this.type = type;
+        this.mType = type;
     }
 
     /**
@@ -146,7 +163,7 @@
      * @return the type for this restriction
      */
     public int getType() {
-        return type;
+        return mType;
     }
 
     /**
@@ -155,7 +172,7 @@
      * single string values.
      */
     public String getSelectedString() {
-        return currentValue;
+        return mCurrentValue;
     }
 
     /**
@@ -164,7 +181,7 @@
      *  null otherwise.
      */
     public String[] getAllSelectedStrings() {
-        return currentValues;
+        return mCurrentValues;
     }
 
     /**
@@ -172,7 +189,23 @@
      * @return the current selected state of the entry.
      */
     public boolean getSelectedState() {
-        return Boolean.parseBoolean(currentValue);
+        return Boolean.parseBoolean(mCurrentValue);
+    }
+
+    /**
+     * Returns the value of the entry as an integer when the type is {@link #TYPE_INTEGER}.
+     * @return the integer value of the entry.
+     */
+    public int getIntValue() {
+        return Integer.parseInt(mCurrentValue);
+    }
+
+    /**
+     * Sets the integer value of the entry when the type is {@link #TYPE_INTEGER}.
+     * @param value the integer value to set.
+     */
+    public void setIntValue(int value) {
+        mCurrentValue = Integer.toString(value);
     }
 
     /**
@@ -181,7 +214,7 @@
      * @param selectedString the string value to select.
      */
     public void setSelectedString(String selectedString) {
-        currentValue = selectedString;
+        mCurrentValue = selectedString;
     }
 
     /**
@@ -190,7 +223,7 @@
      * @param state the current selected state
      */
     public void setSelectedState(boolean state) {
-        currentValue = Boolean.toString(state);
+        mCurrentValue = Boolean.toString(state);
     }
 
     /**
@@ -199,7 +232,7 @@
      * @param allSelectedStrings the current list of selected values.
      */
     public void setAllSelectedStrings(String[] allSelectedStrings) {
-        currentValues = allSelectedStrings;
+        mCurrentValues = allSelectedStrings;
     }
 
     /**
@@ -216,7 +249,7 @@
      * @see #getAllSelectedStrings()
      */
     public void setChoiceValues(String[] choiceValues) {
-        values = choiceValues;
+        mChoiceValues = choiceValues;
     }
 
     /**
@@ -227,7 +260,7 @@
      * @see #setChoiceValues(String[])
      */
     public void setChoiceValues(Context context, int stringArrayResId) {
-        values = context.getResources().getStringArray(stringArrayResId);
+        mChoiceValues = context.getResources().getStringArray(stringArrayResId);
     }
 
     /**
@@ -235,7 +268,7 @@
      * @return the list of possible values.
      */
     public String[] getChoiceValues() {
-        return values;
+        return mChoiceValues;
     }
 
     /**
@@ -248,7 +281,7 @@
      * @see #setChoiceValues(String[])
      */
     public void setChoiceEntries(String[] choiceEntries) {
-        choices = choiceEntries;
+        mChoiceEntries = choiceEntries;
     }
 
     /** Sets a list of strings that will be presented as choices to the user. This is similar to
@@ -257,7 +290,7 @@
      * @param stringArrayResId the resource id of a string array containing the possible entries.
      */
     public void setChoiceEntries(Context context, int stringArrayResId) {
-        choices = context.getResources().getStringArray(stringArrayResId);
+        mChoiceEntries = context.getResources().getStringArray(stringArrayResId);
     }
 
     /**
@@ -265,7 +298,7 @@
      * @return the list of choices presented to the user.
      */
     public String[] getChoiceEntries() {
-        return choices;
+        return mChoiceEntries;
     }
 
     /**
@@ -273,7 +306,7 @@
      * @return the user-visible description, null if none was set earlier.
      */
     public String getDescription() {
-        return description;
+        return mDescription;
     }
 
     /**
@@ -283,7 +316,7 @@
      * @param description the user-visible description string.
      */
     public void setDescription(String description) {
-        this.description = description;
+        this.mDescription = description;
     }
 
     /**
@@ -291,7 +324,7 @@
      * @return the key for the restriction.
      */
     public String getKey() {
-        return key;
+        return mKey;
     }
 
     /**
@@ -299,7 +332,7 @@
      * @return the user-visible title for the entry, null if none was set earlier.
      */
     public String getTitle() {
-        return title;
+        return mTitle;
     }
 
     /**
@@ -307,7 +340,7 @@
      * @param title the user-visible title for the entry.
      */
     public void setTitle(String title) {
-        this.title = title;
+        this.mTitle = title;
     }
 
     private boolean equalArrays(String[] one, String[] other) {
@@ -324,23 +357,23 @@
         if (!(o instanceof RestrictionEntry)) return false;
         final RestrictionEntry other = (RestrictionEntry) o;
         // Make sure that either currentValue matches or currentValues matches.
-        return type == other.type && key.equals(other.key)
+        return mType == other.mType && mKey.equals(other.mKey)
                 &&
-                ((currentValues == null && other.currentValues == null
-                  && currentValue != null && currentValue.equals(other.currentValue))
+                ((mCurrentValues == null && other.mCurrentValues == null
+                  && mCurrentValue != null && mCurrentValue.equals(other.mCurrentValue))
                  ||
-                 (currentValue == null && other.currentValue == null
-                  && currentValues != null && equalArrays(currentValues, other.currentValues)));
+                 (mCurrentValue == null && other.mCurrentValue == null
+                  && mCurrentValues != null && equalArrays(mCurrentValues, other.mCurrentValues)));
     }
 
     @Override
     public int hashCode() {
         int result = 17;
-        result = 31 * result + key.hashCode();
-        if (currentValue != null) {
-            result = 31 * result + currentValue.hashCode();
-        } else if (currentValues != null) {
-            for (String value : currentValues) {
+        result = 31 * result + mKey.hashCode();
+        if (mCurrentValue != null) {
+            result = 31 * result + mCurrentValue.hashCode();
+        } else if (mCurrentValues != null) {
+            for (String value : mCurrentValues) {
                 if (value != null) {
                     result = 31 * result + value.hashCode();
                 }
@@ -359,14 +392,14 @@
     }
 
     public RestrictionEntry(Parcel in) {
-        type = in.readInt();
-        key = in.readString();
-        title = in.readString();
-        description = in.readString();
-        choices = readArray(in);
-        values = readArray(in);
-        currentValue = in.readString();
-        currentValues = readArray(in);
+        mType = in.readInt();
+        mKey = in.readString();
+        mTitle = in.readString();
+        mDescription = in.readString();
+        mChoiceEntries = readArray(in);
+        mChoiceValues = readArray(in);
+        mCurrentValue = in.readString();
+        mCurrentValues = readArray(in);
     }
 
     @Override
@@ -387,14 +420,14 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(type);
-        dest.writeString(key);
-        dest.writeString(title);
-        dest.writeString(description);
-        writeArray(dest, choices);
-        writeArray(dest, values);
-        dest.writeString(currentValue);
-        writeArray(dest, currentValues);
+        dest.writeInt(mType);
+        dest.writeString(mKey);
+        dest.writeString(mTitle);
+        dest.writeString(mDescription);
+        writeArray(dest, mChoiceEntries);
+        writeArray(dest, mChoiceValues);
+        dest.writeString(mCurrentValue);
+        writeArray(dest, mCurrentValues);
     }
 
     public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() {
@@ -409,6 +442,6 @@
 
     @Override
     public String toString() {
-        return "RestrictionsEntry {type=" + type + ", key=" + key + ", value=" + currentValue + "}";
+        return "RestrictionsEntry {type=" + mType + ", key=" + mKey + ", value=" + mCurrentValue + "}";
     }
 }
diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java
new file mode 100644
index 0000000..0dd0edd
--- /dev/null
+++ b/core/java/android/content/RestrictionsManager.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.app.admin.DevicePolicyManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides a mechanism for apps to query restrictions imposed by an entity that
+ * manages the user. Apps can also send permission requests to a local or remote
+ * device administrator to override default app-specific restrictions or any other
+ * operation that needs explicit authorization from the administrator.
+ * <p>
+ * Apps can expose a set of restrictions via a runtime receiver mechanism or via
+ * static meta data in the manifest.
+ * <p>
+ * If the user has an active restrictions provider, dynamic requests can be made in
+ * addition to the statically imposed restrictions. Dynamic requests are app-specific
+ * and can be expressed via a predefined set of templates.
+ * <p>
+ * The RestrictionsManager forwards the dynamic requests to the active
+ * restrictions provider. The restrictions provider can respond back to requests by calling
+ * {@link #notifyPermissionResponse(String, Bundle)}, when
+ * a response is received from the administrator of the device or user 
+ * The response is relayed back to the application via a protected broadcast,
+ * {@link #ACTION_PERMISSION_RESPONSE_RECEIVED}.
+ * <p>
+ * Static restrictions are specified by an XML file referenced by a meta-data attribute
+ * in the manifest. This enables applications as well as any web administration consoles
+ * to be able to read the template from the apk.
+ * <p>
+ * The syntax of the XML format is as follows:
+ * <pre>
+ * &lt;restrictions&gt;
+ *     &lt;restriction
+ *         android:key="&lt;key&gt;"
+ *         android:restrictionType="boolean|string|integer|multi-select|null"
+ *         ... /&gt;
+ *     &lt;restriction ... /&gt;
+ * &lt;/restrictions&gt;
+ * </pre>
+ * <p>
+ * The attributes for each restriction depend on the restriction type.
+ *
+ * @see RestrictionEntry
+ */
+public class RestrictionsManager {
+
+    /**
+     * Broadcast intent delivered when a response is received for a permission
+     * request. The response is not available for later query, so the receiver
+     * must persist and/or immediately act upon the response. The application
+     * should not interrupt the user by coming to the foreground if it isn't
+     * currently in the foreground. It can post a notification instead, informing
+     * the user of a change in state.
+     * <p>
+     * For instance, if the user requested permission to make an in-app purchase,
+     * the app can post a notification that the request had been granted or denied,
+     * and allow the purchase to go through.
+     * <p>
+     * The broadcast Intent carries the following extra:
+     * {@link #EXTRA_RESPONSE_BUNDLE}.
+     */
+    public static final String ACTION_PERMISSION_RESPONSE_RECEIVED =
+            "android.intent.action.PERMISSION_RESPONSE_RECEIVED";
+
+    /**
+     * Protected broadcast intent sent to the active restrictions provider. The intent
+     * contains the following extras:<p>
+     * <ul>
+     * <li>{@link #EXTRA_PACKAGE_NAME} : String; the package name of the application requesting
+     * permission.</li>
+     * <li>{@link #EXTRA_TEMPLATE_ID} : String; the template of the request.</li>
+     * <li>{@link #EXTRA_REQUEST_BUNDLE} : Bundle; contains the template-specific keys and values
+     * for the request.
+     * </ul>
+     * @see DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName)
+     * @see #requestPermission(String, String, Bundle)
+     */
+    public static final String ACTION_REQUEST_PERMISSION =
+            "android.intent.action.REQUEST_PERMISSION";
+
+    /**
+     * The package name of the application making the request.
+     */
+    public static final String EXTRA_PACKAGE_NAME = "package_name";
+
+    /**
+     * The template id that specifies what kind of a request it is and may indicate
+     * how the request is to be presented to the administrator. Must be either one of
+     * the predefined templates or a custom one specified by the application that the
+     * restrictions provider is familiar with.
+     */
+    public static final String EXTRA_TEMPLATE_ID = "template_id";
+
+    /**
+     * A bundle containing the details about the request. The contents depend on the
+     * template id.
+     * @see #EXTRA_TEMPLATE_ID
+     */
+    public static final String EXTRA_REQUEST_BUNDLE = "request_bundle";
+
+    /**
+     * Contains a response from the administrator for specific request.
+     * The bundle contains the following information, at least:
+     * <ul>
+     * <li>{@link #REQUEST_KEY_ID}: The request id.</li>
+     * <li>{@link #REQUEST_KEY_DATA}: The request reference data.</li>
+     * </ul>
+     * <p>
+     * And depending on what the request template was, the bundle will contain the actual
+     * result of the request. For {@link #REQUEST_TEMPLATE_QUESTION}, the result will be in
+     * {@link #RESPONSE_KEY_BOOLEAN}, which is of type boolean; true if the administrator
+     * approved the request, false otherwise.
+     */
+    public static final String EXTRA_RESPONSE_BUNDLE = "response_bundle";
+
+
+    /**
+     * Request template that presents a simple question, with a possible title and icon.
+     * <p>
+     * Required keys are
+     * {@link #REQUEST_KEY_ID} and {@link #REQUEST_KEY_MESSAGE}.
+     * <p>
+     * Optional keys are
+     * {@link #REQUEST_KEY_DATA}, {@link #REQUEST_KEY_ICON}, {@link #REQUEST_KEY_TITLE},
+     * {@link #REQUEST_KEY_APPROVE_LABEL} and {@link #REQUEST_KEY_DENY_LABEL}.
+     */
+    public static final String REQUEST_TEMPLATE_QUESTION = "android.req_template.type.simple";
+
+    /**
+     * Key for request ID contained in the request bundle.
+     * <p>
+     * App-generated request id to identify the specific request when receiving
+     * a response. This value is returned in the {@link #EXTRA_RESPONSE_BUNDLE}.
+     * <p>
+     * Type: String
+     */
+    public static final String REQUEST_KEY_ID = "android.req_template.req_id";
+
+    /**
+     * Key for request data contained in the request bundle.
+     * <p>
+     * Optional, typically used to identify the specific data that is being referred to,
+     * such as the unique identifier for a movie or book. This is not used for display
+     * purposes and is more like a cookie. This value is returned in the
+     * {@link #EXTRA_RESPONSE_BUNDLE}.
+     * <p>
+     * Type: String
+     */
+    public static final String REQUEST_KEY_DATA = "android.req_template.data";
+
+    /**
+     * Key for request title contained in the request bundle.
+     * <p>
+     * Optional, typically used as the title of any notification or dialog presented
+     * to the administrator who approves the request.
+     * <p>
+     * Type: String
+     */
+    public static final String REQUEST_KEY_TITLE = "android.req_template.title";
+
+    /**
+     * Key for request message contained in the request bundle.
+     * <p>
+     * Required, shown as the actual message in a notification or dialog presented
+     * to the administrator who approves the request.
+     * <p>
+     * Type: String
+     */
+    public static final String REQUEST_KEY_MESSAGE = "android.req_template.mesg";
+
+    /**
+     * Key for request icon contained in the request bundle.
+     * <p>
+     * Optional, shown alongside the request message presented to the administrator
+     * who approves the request.
+     * <p>
+     * Type: Bitmap
+     */
+    public static final String REQUEST_KEY_ICON = "android.req_template.icon";
+
+    /**
+     * Key for request approval button label contained in the request bundle.
+     * <p>
+     * Optional, may be shown as a label on the positive button in a dialog or
+     * notification presented to the administrator who approves the request.
+     * <p>
+     * Type: String
+     */
+    public static final String REQUEST_KEY_APPROVE_LABEL = "android.req_template.accept";
+
+    /**
+     * Key for request rejection button label contained in the request bundle.
+     * <p>
+     * Optional, may be shown as a label on the negative button in a dialog or
+     * notification presented to the administrator who approves the request.
+     * <p>
+     * Type: String
+     */
+    public static final String REQUEST_KEY_DENY_LABEL = "android.req_template.reject";
+
+    /**
+     * Key for requestor's name contained in the request bundle. This value is not specified by
+     * the application. It is automatically inserted into the Bundle by the Restrictions Provider
+     * before it is sent to the administrator.
+     * <p>
+     * Type: String
+     */
+    public static final String REQUEST_KEY_REQUESTOR_NAME = "android.req_template.requestor";
+
+    /**
+     * Key for requestor's device name contained in the request bundle. This value is not specified
+     * by the application. It is automatically inserted into the Bundle by the Restrictions Provider
+     * before it is sent to the administrator.
+     * <p>
+     * Type: String
+     */
+    public static final String REQUEST_KEY_DEVICE_NAME = "android.req_template.device";
+
+    /**
+     * Key for the response in the response bundle sent to the application, for a permission
+     * request.
+     * <p>
+     * Type: boolean
+     */
+    public static final String RESPONSE_KEY_BOOLEAN = "android.req_template.response";
+
+    private static final String TAG = "RestrictionsManager";
+
+    private final Context mContext;
+    private final IRestrictionsManager mService;
+
+    /**
+     * @hide
+     */
+    public RestrictionsManager(Context context, IRestrictionsManager service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Returns any available set of application-specific restrictions applicable
+     * to this application.
+     * @return
+     */
+    public Bundle getApplicationRestrictions() {
+        try {
+            if (mService != null) {
+                return mService.getApplicationRestrictions(mContext.getPackageName());
+            }
+        } catch (RemoteException re) {
+            Log.w(TAG, "Couldn't reach service");
+        }
+        return null;
+    }
+
+    /**
+     * Called by an application to check if permission requests can be made. If false,
+     * there is no need to request permission for an operation, unless a static
+     * restriction applies to that operation.
+     * @return
+     */
+    public boolean hasRestrictionsProvider() {
+        try {
+            if (mService != null) {
+                return mService.hasRestrictionsProvider();
+            }
+        } catch (RemoteException re) {
+            Log.w(TAG, "Couldn't reach service");
+        }
+        return false;
+    }
+
+    /**
+     * Called by an application to request permission for an operation. The contents of the
+     * request are passed in a Bundle that contains several pieces of data depending on the
+     * chosen request template.
+     *
+     * @param requestTemplate The request template to use. The template could be one of the
+     * predefined templates specified in this class or a custom template that the specific
+     * Restrictions Provider might understand. For custom templates, the template name should be
+     * namespaced to avoid collisions with predefined templates and templates specified by
+     * other Restrictions Provider vendors.
+     * @param requestData A Bundle containing the data corresponding to the specified request
+     * template. The keys for the data in the bundle depend on the kind of template chosen.
+     */
+    public void requestPermission(String requestTemplate, Bundle requestData) {
+        try {
+            if (mService != null) {
+                mService.requestPermission(mContext.getPackageName(), requestTemplate, requestData);
+            }
+        } catch (RemoteException re) {
+            Log.w(TAG, "Couldn't reach service");
+        }
+    }
+
+    /**
+     * Called by the Restrictions Provider when a response is available to be
+     * delivered to an application.
+     * @param packageName
+     * @param response
+     */
+    public void notifyPermissionResponse(String packageName, Bundle response) {
+        try {
+            if (mService != null) {
+                mService.notifyPermissionResponse(packageName, response);
+            }
+        } catch (RemoteException re) {
+            Log.w(TAG, "Couldn't reach service");
+        }
+    }
+
+    /**
+     * Parse and return the list of restrictions defined in the manifest for the specified
+     * package, if any.
+     * @param packageName The application for which to fetch the restrictions list.
+     * @return The list of RestrictionEntry objects created from the XML file specified
+     * in the manifest, or null if none was specified.
+     */
+    public List<RestrictionEntry> getManifestRestrictions(String packageName) {
+        // TODO:
+        return null;
+    }
+}
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
index 6b4404d..46c9234 100644
--- a/core/java/android/content/SharedPreferences.java
+++ b/core/java/android/content/SharedPreferences.java
@@ -355,7 +355,14 @@
     
     /**
      * Registers a callback to be invoked when a change happens to a preference.
-     * 
+     *
+     * <p class="caution"><strong>Caution:</strong> The preference manager does
+     * not currently store a strong reference to the listener. You must store a
+     * strong reference to the listener, or it will be susceptible to garbage
+     * collection. We recommend you keep a reference to the listener in the
+     * instance data of an object that will exist as long as you need the
+     * listener.</p>
+     *
      * @param listener The callback that will run.
      * @see #unregisterOnSharedPreferenceChangeListener
      */
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 6cb781f..70668e1 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -112,7 +112,7 @@
 
     ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags, int userId);
 
-    boolean canForwardTo(in Intent intent, String resolvedType, int userIdFrom, int userIdDest);
+    boolean canForwardTo(in Intent intent, String resolvedType, int sourceUserId, int targetUserId);
 
     List<ResolveInfo> queryIntentActivities(in Intent intent, 
             String resolvedType, int flags, int userId);
@@ -248,10 +248,10 @@
 
     void clearPackagePersistentPreferredActivities(String packageName, int userId);
 
-    void addForwardingIntentFilter(in IntentFilter filter, boolean removable, int userIdOrig,
-            int userIdDest);
+    void addCrossProfileIntentFilter(in IntentFilter filter, boolean removable, int sourceUserId,
+            int targetUserId);
 
-    void clearForwardingIntentFilters(int userIdOrig);
+    void clearCrossProfileIntentFilters(int sourceUserId);
 
     /**
      * Report the set of 'Home' activity candidates, plus (if any) which of them
@@ -433,6 +433,13 @@
             in VerificationParams verificationParams,
             in ContainerEncryptionParams encryptionParams);
 
+    void installPackageWithVerificationEncryptionAndAbiOverrideEtc(in Uri packageURI,
+            in IPackageInstallObserver observer, in IPackageInstallObserver2 observer2,
+            int flags, in String installerPackageName,
+            in VerificationParams verificationParams,
+            in ContainerEncryptionParams encryptionParams,
+	    in String packageAbiOverride);
+
     int installExistingPackageAsUser(String packageName, int userId);
 
     void verifyPendingInstall(int id, int verificationCode);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index d7bd473..4672015 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -19,9 +19,12 @@
 import android.app.PackageInstallObserver;
 import android.app.PackageUninstallObserver;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.FileBridge;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 
+import java.io.OutputStream;
+
 /** {@hide} */
 public class PackageInstaller {
     private final PackageManager mPm;
@@ -127,10 +130,17 @@
             }
         }
 
-        public ParcelFileDescriptor openWrite(String overlayName, long offsetBytes,
-                long lengthBytes) {
+        /**
+         * Open an APK file for writing, starting at the given offset. You can
+         * then stream data into the file, periodically calling
+         * {@link OutputStream#flush()} to ensure bytes have been written to
+         * disk.
+         */
+        public OutputStream openWrite(String splitName, long offsetBytes, long lengthBytes) {
             try {
-                return mSession.openWrite(overlayName, offsetBytes, lengthBytes);
+                final ParcelFileDescriptor clientSocket = mSession.openWrite(splitName,
+                        offsetBytes, lengthBytes);
+                return new FileBridge.FileBridgeOutputStream(clientSocket.getFileDescriptor());
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 0ba7180..550c1f1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -520,7 +520,7 @@
      * Installation return code: this is passed to the {@link IPackageInstallObserver} by
      * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
      * the package being installed contains native code, but none that is
-     * compatible with the the device's CPU_ABI.
+     * compatible with the device's CPU_ABI.
      * @hide
      */
     @SystemApi
@@ -1601,7 +1601,7 @@
      * <p>
      * Throws {@link NameNotFoundException} if a package with the given name
      * cannot be found on the system.
-     * 
+     *
      * @param packageName The name of the package to inspect.
      * @return Returns either a fully-qualified Intent that can be used to launch
      *         the main Leanback activity in the package, or null if the package
@@ -1615,7 +1615,7 @@
      * <p>
      * Throws {@link NameNotFoundException} if a package with the given name
      * cannot be found on the system.
-     * 
+     *
      * @param packageName The full name (i.e. com.google.apps.contacts) of the
      *            desired package.
      * @return Returns an int array of the assigned gids, or null if there are
@@ -3454,7 +3454,7 @@
 
 
     /**
-     * Return the the enabled setting for a package component (activity,
+     * Return the enabled setting for a package component (activity,
      * receiver, service, provider).  This returns the last value set by
      * {@link #setComponentEnabledSetting(ComponentName, int, int)}; in most
      * cases this value will be {@link #COMPONENT_ENABLED_STATE_DEFAULT} since
@@ -3492,14 +3492,14 @@
             int newState, int flags);
 
     /**
-     * Return the the enabled setting for an application.  This returns
+     * Return the enabled setting for an application. This returns
      * the last value set by
      * {@link #setApplicationEnabledSetting(String, int, int)}; in most
      * cases this value will be {@link #COMPONENT_ENABLED_STATE_DEFAULT} since
      * the value originally specified in the manifest has not been modified.
      *
-     * @param packageName The component to retrieve.
-     * @return Returns the current enabled state for the component.  May
+     * @param packageName The package name of the application to retrieve.
+     * @return Returns the current enabled state for the application.  May
      * be one of {@link #COMPONENT_ENABLED_STATE_ENABLED},
      * {@link #COMPONENT_ENABLED_STATE_DISABLED}, or
      * {@link #COMPONENT_ENABLED_STATE_DEFAULT}.  The last one means the
@@ -3576,24 +3576,38 @@
     }
 
     /**
-     * Adds a forwarding intent filter. After calling this method all intents sent from the user
-     * with id userIdOrig can also be be resolved by activities in the user with id userIdDest if
-     * they match the specified intent filter.
-     * @param filter the {@link IntentFilter} the intent has to match to be forwarded
-     * @param removable if set to false, {@link clearForwardingIntents} will not remove this intent
-     * filter
-     * @param userIdOrig user from which the intent can be forwarded
-     * @param userIdDest user to which the intent can be forwarded
+     * Adds a {@link CrossProfileIntentFilter}. After calling this method all intents sent from the
+     * user with id sourceUserId can also be be resolved by activities in the user with id
+     * targetUserId if they match the specified intent filter.
+     * @param filter the {@link IntentFilter} the intent has to match
+     * @param removable if set to false, {@link clearCrossProfileIntentFilters} will not remove this
+     * {@link CrossProfileIntentFilter}
      * @hide
      */
-    public abstract void addForwardingIntentFilter(IntentFilter filter, boolean removable,
-            int userIdOrig, int userIdDest);
+    public abstract void addCrossProfileIntentFilter(IntentFilter filter, boolean removable,
+            int sourceUserId, int targetUserId);
 
     /**
-     * Clearing all removable {@link ForwardingIntentFilter}s that are set with the given user as
-     * the origin.
-     * @param userIdOrig user from which the intent can be forwarded
+     * @hide
+     * @deprecated
+     * TODO: remove it as soon as the code of ManagedProvisionning is updated
+    */
+    public abstract void addForwardingIntentFilter(IntentFilter filter, boolean removable,
+            int sourceUserId, int targetUserId);
+
+    /**
+     * Clearing removable {@link CrossProfileIntentFilter}s which have the specified user as their
+     * source
+     * @param sourceUserId
+     * be cleared.
      * @hide
      */
-    public abstract void clearForwardingIntentFilters(int userIdOrig);
+    public abstract void clearCrossProfileIntentFilters(int sourceUserId);
+
+    /**
+     * @hide
+     * @deprecated
+     * TODO: remove it as soon as the code of ManagedProvisionning is updated
+    */
+    public abstract void clearForwardingIntentFilters(int sourceUserId);
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 8965faa..4cac7fd 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2174,7 +2174,6 @@
         }
 
         final int innerDepth = parser.getDepth();
-
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                 && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
@@ -2551,13 +2550,13 @@
                     com.android.internal.R.styleable.AndroidManifestActivity_singleUser,
                     false)) {
                 a.info.flags |= ActivityInfo.FLAG_SINGLE_USER;
-                if (a.info.exported) {
+                if (a.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
                     Slog.w(TAG, "Activity exported request ignored due to singleUser: "
                             + a.className + " at " + mArchiveSourcePath + " "
                             + parser.getPositionDescription());
                     a.info.exported = false;
+                    setExported = true;
                 }
-                setExported = true;
             }
             if (sa.getBoolean(
                     com.android.internal.R.styleable.AndroidManifestActivity_primaryUserOnly,
@@ -2910,7 +2909,7 @@
                 com.android.internal.R.styleable.AndroidManifestProvider_singleUser,
                 false)) {
             p.info.flags |= ProviderInfo.FLAG_SINGLE_USER;
-            if (p.info.exported) {
+            if (p.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
                 Slog.w(TAG, "Provider exported request ignored due to singleUser: "
                         + p.className + " at " + mArchiveSourcePath + " "
                         + parser.getPositionDescription());
@@ -3184,13 +3183,13 @@
                 com.android.internal.R.styleable.AndroidManifestService_singleUser,
                 false)) {
             s.info.flags |= ServiceInfo.FLAG_SINGLE_USER;
-            if (s.info.exported) {
+            if (s.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
                 Slog.w(TAG, "Service exported request ignored due to singleUser: "
                         + s.className + " at " + mArchiveSourcePath + " "
                         + parser.getPositionDescription());
                 s.info.exported = false;
+                setExported = true;
             }
-            setExported = true;
         }
 
         sa.recycle();
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index c593e9e..c8de2f1 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -142,9 +142,10 @@
     public static final String STRING_TYPE_TEMPERATURE = "android.sensor.temperature";
 
     /**
-     * A constant describing a proximity sensor type.
+     * A constant describing a proximity sensor type. This is a wake up sensor.
      * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
      * for more details.
+     * @see #isWakeUpSensor()
      */
     public static final int TYPE_PROXIMITY = 8;
 
@@ -307,8 +308,10 @@
      * itself. The sensor continues to operate while the device is asleep
      * and will automatically wake the device to notify when significant
      * motion is detected. The application does not need to hold any wake
-     * locks for this sensor to trigger.
+     * locks for this sensor to trigger. This is a wake up sensor.
      * <p>See {@link TriggerEvent} for more details.
+     *
+     * @see #isWakeUpSensor()
      */
     public static final int TYPE_SIGNIFICANT_MOTION = 17;
 
@@ -381,11 +384,17 @@
     /**
      * A constant describing a heart rate monitor.
      * <p>
-     * A sensor that measures the heart rate in beats per minute.
+     * The reported value is the heart rate in beats per minute.
      * <p>
-     * value[0] represents the beats per minute when the measurement was taken.
-     * value[0] is 0 if the heart rate monitor could not measure the rate or the
-     * rate is 0 beat per minute.
+     * The reported accuracy represents the status of the monitor during the reading. See the
+     * {@code SENSOR_STATUS_*} constants in {@link android.hardware.SensorManager SensorManager}
+     * for more details on accuracy/status values. In particular, when the accuracy is
+     * {@code SENSOR_STATUS_UNRELIABLE} or {@code SENSOR_STATUS_NO_CONTACT}, the heart rate
+     * value should be discarded.
+     * <p>
+     * This sensor requires permission {@code android.permission.BODY_SENSORS}.
+     * It will not be returned by {@code SensorManager.getSensorsList} nor
+     * {@code SensorManager.getDefaultSensor} if the application doesn't have this permission.
      */
     public static final int TYPE_HEART_RATE = 21;
 
@@ -397,6 +406,321 @@
     public static final String STRING_TYPE_HEART_RATE = "android.sensor.heart_rate";
 
     /**
+     * A non-wake up variant of proximity sensor.
+     *
+     * @see #TYPE_PROXIMITY
+     */
+    public static final int TYPE_NON_WAKE_UP_PROXIMITY_SENSOR = 22;
+
+    /**
+     * A constant string describing a non-wake up proximity sensor type.
+     *
+     * @see #TYPE_NON_WAKE_UP_PROXIMITY_SENSOR
+     */
+    public static final String SENSOR_STRING_TYPE_NON_WAKE_UP_PROXIMITY_SENSOR =
+            "android.sensor.non_wake_up_proximity_sensor";
+
+    /**
+     * A constant describing a wake up variant of accelerometer sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_ACCELEROMETER
+     */
+    public static final int TYPE_WAKE_UP_ACCELEROMETER = 23;
+
+    /**
+     * A constant string describing a wake up accelerometer.
+     *
+     * @see #TYPE_WAKE_UP_ACCELEROMETER
+     */
+    public static final String STRING_TYPE_WAKE_UP_ACCELEROMETER =
+            "android.sensor.wake_up_accelerometer";
+
+    /**
+     * A constant describing a wake up variant of a magnetic field sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_MAGNETIC_FIELD
+     */
+    public static final int TYPE_WAKE_UP_MAGNETIC_FIELD = 24;
+
+    /**
+     * A constant string describing a wake up magnetic field sensor.
+     *
+     * @see #TYPE_WAKE_UP_MAGNETIC_FIELD
+     */
+    public static final String STRING_TYPE_WAKE_UP_MAGNETIC_FIELD =
+            "android.sensor.wake_up_magnetic_field";
+
+    /**
+     * A constant describing a wake up variant of an orientation sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_ORIENTATION
+     */
+    public static final int TYPE_WAKE_UP_ORIENTATION = 25;
+
+    /**
+     * A constant string describing a wake up orientation sensor.
+     *
+     * @see #TYPE_WAKE_UP_ORIENTATION
+     */
+    public static final String STRING_TYPE_WAKE_UP_ORIENTATION =
+            "android.sensor.wake_up_orientation";
+
+    /**
+     * A constant describing a wake up variant of a gyroscope sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_GYROSCOPE
+     */
+    public static final int TYPE_WAKE_UP_GYROSCOPE = 26;
+
+    /**
+     * A constant string describing a wake up gyroscope sensor type.
+     *
+     * @see #TYPE_WAKE_UP_GYROSCOPE
+     */
+    public static final String STRING_TYPE_WAKE_UP_GYROSCOPE = "android.sensor.wake_up_gyroscope";
+
+    /**
+     * A constant describing a wake up variant of a light sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_LIGHT
+     */
+    public static final int TYPE_WAKE_UP_LIGHT = 27;
+
+    /**
+     * A constant string describing a wake up light sensor type.
+     *
+     * @see #TYPE_WAKE_UP_LIGHT
+     */
+    public static final String STRING_TYPE_WAKE_UP_LIGHT = "android.sensor.wake_up_light";
+
+    /**
+     * A constant describing a wake up variant of a pressure sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_PRESSURE
+     */
+    public static final int TYPE_WAKE_UP_PRESSURE = 28;
+
+    /**
+     * A constant string describing a wake up pressure sensor type.
+     *
+     * @see #TYPE_WAKE_UP_PRESSURE
+     */
+    public static final String STRING_TYPE_WAKE_UP_PRESSURE = "android.sensor.wake_up_pressure";
+
+    /**
+     * A constant describing a wake up variant of a gravity sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_GRAVITY
+     */
+    public static final int TYPE_WAKE_UP_GRAVITY = 29;
+
+    /**
+     * A constant string describing a wake up gravity sensor type.
+     *
+     * @see #TYPE_WAKE_UP_GRAVITY
+     */
+    public static final String STRING_TYPE_WAKE_UP_GRAVITY = "android.sensor.wake_up_gravity";
+
+    /**
+     * A constant describing a wake up variant of a linear acceleration sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_LINEAR_ACCELERATION
+     */
+    public static final int TYPE_WAKE_UP_LINEAR_ACCELERATION = 30;
+
+    /**
+     * A constant string describing a wake up linear acceleration sensor type.
+     *
+     * @see #TYPE_WAKE_UP_LINEAR_ACCELERATION
+     */
+    public static final String STRING_TYPE_WAKE_UP_LINEAR_ACCELERATION =
+            "android.sensor.wake_up_linear_acceleration";
+
+    /**
+     * A constant describing a wake up variant of a rotation vector sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_ROTATION_VECTOR
+     */
+    public static final int TYPE_WAKE_UP_ROTATION_VECTOR = 31;
+
+    /**
+     * A constant string describing a wake up rotation vector sensor type.
+     *
+     * @see #TYPE_WAKE_UP_ROTATION_VECTOR
+     */
+    public static final String STRING_TYPE_WAKE_UP_ROTATION_VECTOR =
+            "android.sensor.wake_up_rotation_vector";
+
+    /**
+     * A constant describing a wake up variant of a relative humidity sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_RELATIVE_HUMIDITY
+     */
+    public static final int TYPE_WAKE_UP_RELATIVE_HUMIDITY = 32;
+
+    /**
+     * A constant string describing a wake up relative humidity sensor type.
+     *
+     * @see #TYPE_WAKE_UP_RELATIVE_HUMIDITY
+     */
+    public static final String STRING_TYPE_WAKE_UP_RELATIVE_HUMIDITY =
+            "android.sensor.wake_up_relative_humidity";
+
+    /**
+     * A constant describing a wake up variant of an ambient temperature sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_AMBIENT_TEMPERATURE
+     */
+    public static final int TYPE_WAKE_UP_AMBIENT_TEMPERATURE = 33;
+
+    /**
+     * A constant string describing a wake up ambient temperature sensor type.
+     *
+     * @see #TYPE_WAKE_UP_AMBIENT_TEMPERATURE
+     */
+    public static final String STRING_TYPE_WAKE_UP_AMBIENT_TEMPERATURE =
+            "android.sensor.wake_up_ambient_temperature";
+
+    /**
+     * A constant describing a wake up variant of an uncalibrated magnetic field sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_MAGNETIC_FIELD_UNCALIBRATED
+     */
+    public static final int TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED = 34;
+
+    /**
+     * A constant string describing a wake up uncalibrated magnetic field sensor type.
+     *
+     * @see #TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED
+     */
+    public static final String STRING_TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED =
+            "android.sensor.wake_up_magnetic_field_uncalibrated";
+
+    /**
+     * A constant describing a wake up variant of a game rotation vector sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_GAME_ROTATION_VECTOR
+     */
+    public static final int TYPE_WAKE_UP_GAME_ROTATION_VECTOR = 35;
+
+    /**
+     * A constant string describing a wake up game rotation vector sensor type.
+     *
+     * @see #TYPE_WAKE_UP_GAME_ROTATION_VECTOR
+     */
+    public static final String STRING_TYPE_WAKE_UP_GAME_ROTATION_VECTOR =
+            "android.sensor.wake_up_game_rotation_vector";
+
+    /**
+     * A constant describing a wake up variant of an uncalibrated gyroscope sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_GYROSCOPE_UNCALIBRATED
+     */
+    public static final int TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED = 36;
+
+    /**
+     * A constant string describing a wake up uncalibrated gyroscope sensor type.
+     *
+     * @see #TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED
+     */
+    public static final String STRING_TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED =
+            "android.sensor.wake_up_gyroscope_uncalibrated";
+
+    /**
+     * A constant describing a wake up variant of a step detector sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_STEP_DETECTOR
+     */
+    public static final int TYPE_WAKE_UP_STEP_DETECTOR = 37;
+
+    /**
+     * A constant string describing a wake up step detector sensor type.
+     *
+     * @see #TYPE_WAKE_UP_STEP_DETECTOR
+     */
+    public static final String STRING_TYPE_WAKE_UP_STEP_DETECTOR =
+            "android.sensor.wake_up_step_detector";
+
+    /**
+     * A constant describing a wake up variant of a step counter sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_STEP_COUNTER
+     */
+    public static final int TYPE_WAKE_UP_STEP_COUNTER = 38;
+
+    /**
+     * A constant string describing a wake up step counter sensor type.
+     *
+     * @see #TYPE_WAKE_UP_STEP_COUNTER
+     */
+    public static final String STRING_TYPE_WAKE_UP_STEP_COUNTER =
+            "android.sensor.wake_up_step_counter";
+
+    /**
+     * A constant describing a wake up variant of a geomagnetic rotation vector sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_GEOMAGNETIC_ROTATION_VECTOR
+     */
+    public static final int TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR = 39;
+
+    /**
+     * A constant string describing a wake up geomagnetic rotation vector sensor type.
+     *
+     * @see #TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR
+     */
+    public static final String STRING_TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR =
+            "android.sensor.wake_up_geomagnetic_rotation_vector";
+
+    /**
+     * A constant describing a wake up variant of a heart rate sensor type.
+     *
+     * @see #isWakeUpSensor()
+     * @see #TYPE_HEART_RATE
+     */
+    public static final int TYPE_WAKE_UP_HEART_RATE = 40;
+
+    /**
+     * A constant string describing a wake up heart rate sensor type.
+     *
+     * @see #TYPE_WAKE_UP_HEART_RATE
+     */
+    public static final String STRING_TYPE_WAKE_UP_HEART_RATE = "android.sensor.wake_up_heart_rate";
+
+    /**
+     * A sensor of this type generates an event each time a tilt event is detected. A tilt event
+     * is generated if the direction of the 2-seconds window average gravity changed by at
+     * least 35 degrees since the activation of the sensor. It is a wake up sensor.
+     *
+     * @see #isWakeUpSensor()
+     */
+    public static final int TYPE_WAKE_UP_TILT_DETECTOR = 41;
+
+    /**
+     * A constant string describing a wake up tilt detector sensor type.
+     *
+     * @see #TYPE_WAKE_UP_TILT_DETECTOR
+     */
+    public static final String SENSOR_STRING_TYPE_WAKE_UP_TILT_DETECTOR =
+            "android.sensor.wake_up_tilt_detector";
+
+    /**
      * A constant describing a wake gesture sensor.
      * <p>
      * Wake gesture sensors enable waking up the device based on a device specific motion.
@@ -410,6 +734,7 @@
      * the device. This sensor must be low power, as it is likely to be activated 24/7.
      * Values of events created by this sensors should not be used.
      *
+     * @see #isWakeUpSensor()
      * @hide This sensor is expected to only be used by the power manager
      */
     public static final int TYPE_WAKE_GESTURE = 42;
@@ -467,7 +792,29 @@
             REPORTING_MODE_ON_CHANGE,  1, // SENSOR_TYPE_STEP_DETECTOR
             REPORTING_MODE_ON_CHANGE,  1, // SENSOR_TYPE_STEP_COUNTER
             REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR
-            REPORTING_MODE_ON_CHANGE, 1  // SENSOR_TYPE_HEART_RATE_MONITOR
+            REPORTING_MODE_ON_CHANGE,  1, // SENSOR_TYPE_HEART_RATE_MONITOR
+            REPORTING_MODE_ON_CHANGE,  3, // SENSOR_TYPE_NON_WAKE_UP_PROXIMITY_SENSOR
+            // wake up variants of base sensors
+            REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_ACCELEROMETER
+            REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_MAGNETIC_FIELD
+            REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_ORIENTATION
+            REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_GYROSCOPE
+            REPORTING_MODE_ON_CHANGE,  3, // SENSOR_TYPE_WAKE_UP_LIGHT
+            REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_PRESSURE
+            REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_GRAVITY
+            REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_LINEAR_ACCELERATION
+            REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_WAKE_UP_ROTATION_VECTOR
+            REPORTING_MODE_ON_CHANGE,  3, // SENSOR_TYPE_WAKE_UP_RELATIVE_HUMIDITY
+            REPORTING_MODE_ON_CHANGE,  3, // SENSOR_TYPE_WAKE_UP_AMBIENT_TEMPERATURE
+            REPORTING_MODE_CONTINUOUS, 6, // SENSOR_TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED
+            REPORTING_MODE_CONTINUOUS, 4, // SENSOR_TYPE_WAKE_UP_GAME_ROTATION_VECTOR
+            REPORTING_MODE_CONTINUOUS, 6, // SENSOR_TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED
+            REPORTING_MODE_ON_CHANGE,  1, // SENSOR_TYPE_WAKE_UP_STEP_DETECTOR
+            REPORTING_MODE_ON_CHANGE,  1, // SENSOR_TYPE_WAKE_UP_STEP_COUNTER
+            REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR
+            REPORTING_MODE_ON_CHANGE,  1, // SENSOR_TYPE_WAKE_UP_HEART_RATE_MONITOR
+            REPORTING_MODE_ON_CHANGE,  1, // SENSOR_TYPE_WAKE_UP_TILT_DETECTOR
+            REPORTING_MODE_ONE_SHOT,   1, // SENSOR_TYPE_WAKE_GESTURE
     };
 
     static int getReportingMode(Sensor sensor) {
@@ -525,6 +872,8 @@
     private int     mFifoMaxEventCount;
     private String  mStringType;
     private String  mRequiredPermission;
+    private int     mMaxDelay;
+    private boolean mWakeUpSensor;
 
     Sensor() {
     }
@@ -625,6 +974,51 @@
         return mHandle;
     }
 
+    /**
+     * This value is defined only for continuous mode sensors. It is the delay between two
+     * sensor events corresponding to the lowest frequency that this sensor supports. When
+     * lower frequencies are requested through registerListener() the events will be generated
+     * at this frequency instead. It can be used to estimate when the batch FIFO may be full.
+     * Older devices may set this value to zero. Ignore this value in case it is negative
+     * or zero.
+     *
+     * @return The max delay for this sensor in microseconds.
+     */
+    public int getMaxDelay() {
+        return mMaxDelay;
+    }
+
+    /**
+     * Returns whether this sensor is a wake-up sensor.
+     * <p>
+     * Wake up sensors wake the application processor up when they have events to deliver. When a
+     * wake up sensor is registered to without batching enabled, each event will wake the
+     * application processor up.
+     * <p>
+     * When a wake up sensor is registered to with batching enabled, it
+     * wakes the application processor up when maxReportingLatency has elapsed or when the hardware
+     * FIFO storing the events from wake up sensors is getting full.
+     * <p>
+     * Non-wake up sensors never wake the application processor up. Their events are only reported
+     * when the application processor is awake, for example because the application holds a wake
+     * lock, or another source woke the application processor up.
+     * <p>
+     * When a non-wake up sensor is registered to without batching enabled, the measurements made
+     * while the application processor is asleep might be lost and never returned.
+     * <p>
+     * When a non-wake up sensor is registered to with batching enabled, the measurements made while
+     * the application processor is asleep are stored in the hardware FIFO for non-wake up sensors.
+     * When this FIFO gets full, new events start overwriting older events. When the application
+     * then wakes up, the latest events are returned, and some old events might be lost. The number
+     * of events actually returned depends on the hardware FIFO size, as well as on what other
+     * sensors are activated. If losing sensor events is not acceptable during batching, you must
+     * use the wake-up version of the sensor.
+     * @return true if this is a wake up sensor, false otherwise.
+     */
+    public boolean isWakeUpSensor() {
+        return mWakeUpSensor;
+    }
+
     void setRange(float max, float res) {
         mMaxRange = max;
         mResolution = res;
diff --git a/core/java/android/hardware/SensorEventListener.java b/core/java/android/hardware/SensorEventListener.java
index 677d244..0d859fb 100644
--- a/core/java/android/hardware/SensorEventListener.java
+++ b/core/java/android/hardware/SensorEventListener.java
@@ -39,11 +39,13 @@
     public void onSensorChanged(SensorEvent event);
 
     /**
-     * Called when the accuracy of a sensor has changed.
-     * <p>See {@link android.hardware.SensorManager SensorManager}
-     * for details.
+     * Called when the accuracy of the registered sensor has changed.
      *
-     * @param accuracy The new accuracy of this sensor
+     * <p>See the SENSOR_STATUS_* constants in
+     * {@link android.hardware.SensorManager SensorManager} for details.
+     *
+     * @param accuracy The new accuracy of this sensor, one of
+     *         {@code SensorManager.SENSOR_STATUS_*}
      */
-    public void onAccuracyChanged(Sensor sensor, int accuracy);    
+    public void onAccuracyChanged(Sensor sensor, int accuracy);
 }
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 5f2b5f0..25c7630 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -321,6 +321,13 @@
 
 
     /**
+      * The values returned by this sensor cannot be trusted because the sensor
+      * had no contact with what it was measuring (for example, the heart rate
+      * monitor is not in contact with the user).
+      */
+    public static final int SENSOR_STATUS_NO_CONTACT = -1;
+
+    /**
      * The values returned by this sensor cannot be trusted, calibration is
      * needed or the environment doesn't allow readings
      */
@@ -421,9 +428,10 @@
      * {@link SensorManager#getSensorList(int) getSensorList}.
      *
      * @param type
-     *        of sensors requested
+     *         of sensors requested
      *
-     * @return the default sensors matching the asked type.
+     * @return the default sensor matching the requested type if one exists and the application
+     *         has the necessary permissions, or null otherwise.
      *
      * @see #getSensorList(int)
      * @see Sensor
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 8684a04..b66ec86 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -395,25 +395,12 @@
             t.timestamp = timestamp;
             t.accuracy = inAccuracy;
             t.sensor = sensor;
-            switch (t.sensor.getType()) {
-                // Only report accuracy for sensors that support it.
-                case Sensor.TYPE_MAGNETIC_FIELD:
-                case Sensor.TYPE_ORIENTATION:
-                    // call onAccuracyChanged() only if the value changes
-                    final int accuracy = mSensorAccuracies.get(handle);
-                    if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
-                        mSensorAccuracies.put(handle, t.accuracy);
-                        mListener.onAccuracyChanged(t.sensor, t.accuracy);
-                    }
-                    break;
-                default:
-                    // For other sensors, just report the accuracy once
-                    if (mFirstEvent.get(handle) == false) {
-                        mFirstEvent.put(handle, true);
-                        mListener.onAccuracyChanged(
-                                t.sensor, SENSOR_STATUS_ACCURACY_HIGH);
-                    }
-                    break;
+
+            // call onAccuracyChanged() only if the value changes
+            final int accuracy = mSensorAccuracies.get(handle);
+            if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
+                mSensorAccuracies.put(handle, t.accuracy);
+                mListener.onAccuracyChanged(t.sensor, t.accuracy);
             }
             mListener.onSensorChanged(t);
         }
diff --git a/core/java/android/hardware/camera2/CameraAccessException.java b/core/java/android/hardware/camera2/CameraAccessException.java
index 1af575f..ca71e81 100644
--- a/core/java/android/hardware/camera2/CameraAccessException.java
+++ b/core/java/android/hardware/camera2/CameraAccessException.java
@@ -114,7 +114,10 @@
         mReason = problem;
     }
 
-    private static String getDefaultMessage(int problem) {
+    /**
+     * @hide
+     */
+    public static String getDefaultMessage(int problem) {
         switch (problem) {
             case CAMERA_IN_USE:
                 return "The camera device is in use already";
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index a69a813..0901562 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -84,9 +84,8 @@
         try {
             CameraBinderDecorator.throwOnError(
                     CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor());
-        } catch(CameraRuntimeException e) {
-            throw new IllegalStateException("Failed to setup camera vendor tags",
-                    e.asChecked());
+        } catch (CameraRuntimeException e) {
+            handleRecoverableSetupErrors(e, "Failed to set up vendor tags");
         }
 
         try {
@@ -244,7 +243,11 @@
                             USE_CALLING_UID, holder);
                     cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
                 } catch (CameraRuntimeException e) {
-                    if (e.getReason() == CameraAccessException.CAMERA_IN_USE ||
+                    if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) {
+                        // Use legacy camera implementation for HAL1 devices
+                        Log.i(TAG, "Using legacy camera HAL.");
+                        cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id);
+                    } else if (e.getReason() == CameraAccessException.CAMERA_IN_USE ||
                             e.getReason() == CameraAccessException.MAX_CAMERAS_IN_USE ||
                             e.getReason() == CameraAccessException.CAMERA_DISABLED ||
                             e.getReason() == CameraAccessException.CAMERA_DISCONNECTED ||
@@ -441,6 +444,18 @@
         return mDeviceIdList;
     }
 
+    private void handleRecoverableSetupErrors(CameraRuntimeException e, String msg) {
+        int problem = e.getReason();
+        switch (problem) {
+            case CameraAccessException.CAMERA_DISCONNECTED:
+                String errorMsg = CameraAccessException.getDefaultMessage(problem);
+                Log.w(TAG, msg + ": " + errorMsg);
+                break;
+            default:
+                throw new IllegalStateException(msg, e.asChecked());
+        }
+    }
+
     // TODO: this class needs unit tests
     // TODO: extract class into top level
     private class CameraServiceListener extends ICameraServiceListener.Stub {
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
new file mode 100644
index 0000000..2d7af85
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.soundtrigger;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+/**
+ * The SoundTrigger class provides access via JNI to the native service managing
+ * the sound trigger HAL.
+ *
+ * @hide
+ */
+public class SoundTrigger {
+
+    public static final int STATUS_OK = 0;
+    public static final int STATUS_ERROR = Integer.MIN_VALUE;
+    public static final int STATUS_PERMISSION_DENIED = -1;
+    public static final int STATUS_NO_INIT = -19;
+    public static final int STATUS_BAD_VALUE = -22;
+    public static final int STATUS_DEAD_OBJECT = -32;
+    public static final int STATUS_INVALID_OPERATION = -38;
+
+    /*****************************************************************************
+     * A ModuleProperties describes a given sound trigger hardware module
+     * managed by the native sound trigger service. Each module has a unique
+     * ID used to target any API call to this paricular module. Module
+     * properties are returned by listModules() method.
+     ****************************************************************************/
+    public static class ModuleProperties {
+        /** Unique module ID provided by the native service */
+        public final int id;
+
+        /** human readable voice detection engine implementor */
+        public final String implementor;
+
+        /** human readable voice detection engine description */
+        public final String description;
+
+        /** Unique voice engine Id (changes with each version) */
+        public final UUID uuid;
+
+        /** Voice detection engine version */
+        public final int version;
+
+        /** Maximum number of active sound models */
+        public final int maxSoundModels;
+
+        /** Maximum number of key phrases */
+        public final int maxKeyPhrases;
+
+        /** Maximum number of users per key phrase */
+        public final int maxUsers;
+
+        /** Supported recognition modes (bit field, RECOGNITION_MODE_VOICE_TRIGGER ...) */
+        public final int recognitionModes;
+
+        /** Supports seamless transition to capture mode after recognition */
+        public final boolean supportsCaptureTransition;
+
+        /** Maximum buffering capacity in ms if supportsCaptureTransition() is true */
+        public final int maxBufferMs;
+
+        /** Supports capture by other use cases while detection is active */
+        public final boolean supportsConcurrentCapture;
+
+        /** Rated power consumption when detection is active with TDB silence/sound/speech ratio */
+        public final int powerConsumptionMw;
+
+        ModuleProperties(int id, String implementor, String description,
+                String uuid, int version, int maxSoundModels, int maxKeyPhrases,
+                int maxUsers, int recognitionModes, boolean supportsCaptureTransition,
+                int maxBufferMs, boolean supportsConcurrentCapture,
+                int powerConsumptionMw) {
+            this.id = id;
+            this.implementor = implementor;
+            this.description = description;
+            this.uuid = UUID.fromString(uuid);
+            this.version = version;
+            this.maxSoundModels = maxSoundModels;
+            this.maxKeyPhrases = maxKeyPhrases;
+            this.maxUsers = maxUsers;
+            this.recognitionModes = recognitionModes;
+            this.supportsCaptureTransition = supportsCaptureTransition;
+            this.maxBufferMs = maxBufferMs;
+            this.supportsConcurrentCapture = supportsConcurrentCapture;
+            this.powerConsumptionMw = powerConsumptionMw;
+        }
+    }
+
+    /*****************************************************************************
+     * A SoundModel describes the attributes and contains the binary data used by the hardware
+     * implementation to detect a particular sound pattern.
+     * A specialized version {@link KeyPhraseSoundModel} is defined for key phrase
+     * sound models.
+     ****************************************************************************/
+    public static class SoundModel {
+        /** Undefined sound model type */
+        public static final int TYPE_UNKNOWN = -1;
+
+        /** Keyphrase sound model */
+        public static final int TYPE_KEYPHRASE = 0;
+
+        /** Sound model type (e.g. TYPE_KEYPHRASE); */
+        public final int type;
+
+        /** Opaque data. For use by vendor implementation and enrollment application */
+        public final byte[] data;
+
+        public SoundModel(int type, byte[] data) {
+            this.type = type;
+            this.data = data;
+        }
+    }
+
+    /*****************************************************************************
+     * A KeyPhrase describes a key phrase that can be detected by a
+     * {@link KeyPhraseSoundModel}
+     ****************************************************************************/
+    public static class KeyPhrase {
+        /** Recognition modes supported for this key phrase in the model */
+        public final int recognitionModes;
+
+        /** Locale of the keyphrase. JAVA Locale string e.g en_US */
+        public final String locale;
+
+        /** Key phrase text */
+        public final String text;
+
+        /** Number of users this key phrase has been trained for */
+        public final int numUsers;
+
+        public KeyPhrase(int recognitionModes, String locale, String text, int numUsers) {
+            this.recognitionModes = recognitionModes;
+            this.locale = locale;
+            this.text = text;
+            this.numUsers = numUsers;
+        }
+    }
+
+    /*****************************************************************************
+     * A KeyPhraseSoundModel is a specialized {@link SoundModel} for key phrases.
+     * It contains data needed by the hardware to detect a certain number of key phrases
+     * and the list of corresponding {@link KeyPhrase} descriptors.
+     ****************************************************************************/
+    public static class KeyPhraseSoundModel extends SoundModel {
+        /** Key phrases in this sound model */
+        public final KeyPhrase[] keyPhrases; // keyword phrases in model
+
+        public KeyPhraseSoundModel(byte[] data, KeyPhrase[] keyPhrases) {
+            super(TYPE_KEYPHRASE, data);
+            this.keyPhrases = keyPhrases;
+        }
+    }
+
+    /**
+     *  Modes for key phrase recognition
+     */
+    /** Simple recognition of the key phrase */
+    public static final int RECOGNITION_MODE_VOICE_TRIGGER = 0x1;
+    /** Trigger only if one user is identified */
+    public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 0x2;
+    /** Trigger only if one user is authenticated */
+    public static final int RECOGNITION_MODE_USER_AUTHENTICATION = 0x4;
+
+    /**
+     *  Status codes for {@link RecognitionEvent}
+     */
+    /** Recognition success */
+    public static final int RECOGNITION_STATUS_SUCCESS = 0;
+    /** Recognition aborted (e.g. capture preempted by anotehr use case */
+    public static final int RECOGNITION_STATUS_ABORT = 1;
+    /** Recognition failure */
+    public static final int RECOGNITION_STATUS_FAILURE = 2;
+
+    /**
+     *  A RecognitionEvent is provided by the
+     *  {@link StatusListener#onRecognition(RecognitionEvent)}
+     *  callback upon recognition success or failure.
+     */
+    public static class RecognitionEvent {
+        /** Recognition status e.g {@link #RECOGNITION_STATUS_SUCCESS} */
+        public final int status;
+        /** Sound Model corresponding to this event callback */
+        public final int soundModelHandle;
+        /** True if it is possible to capture audio from this utterance buffered by the hardware */
+        public final boolean captureAvailable;
+        /** Audio session ID to be used when capturing the utterance with an AudioRecord
+         * if captureAvailable() is true. */
+        public final int captureSession;
+        /** Delay in ms between end of model detection and start of audio available for capture.
+         * A negative value is possible (e.g. if keyphrase is also available for capture) */
+        public final int captureDelayMs;
+        /** Opaque data for use by system applications who know about voice engine internals,
+         * typically during enrollment. */
+        public final byte[] data;
+
+        RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
+                int captureSession, int captureDelayMs, byte[] data) {
+            this.status = status;
+            this.soundModelHandle = soundModelHandle;
+            this.captureAvailable = captureAvailable;
+            this.captureSession = captureSession;
+            this.captureDelayMs = captureDelayMs;
+            this.data = data;
+        }
+    }
+
+    /**
+     *  Additional data conveyed by a {@link KeyPhraseRecognitionEvent}
+     *  for a key phrase detection.
+     */
+    public static class KeyPhraseRecognitionExtra {
+        /** Confidence level for each user defined in the key phrase in the same order as
+         * users in the key phrase. The confidence level is expressed in percentage (0% -100%) */
+        public final int[] confidenceLevels;
+
+        /** Recognition modes matched for this event */
+        public final int recognitionModes;
+
+        KeyPhraseRecognitionExtra(int[] confidenceLevels, int recognitionModes) {
+            this.confidenceLevels = confidenceLevels;
+            this.recognitionModes = recognitionModes;
+        }
+    }
+
+    /**
+     *  Specialized {@link RecognitionEvent} for a key phrase detection.
+     */
+    public static class KeyPhraseRecognitionEvent extends RecognitionEvent {
+        /** Indicates if the key phrase is present in the buffered audio available for capture */
+        public final KeyPhraseRecognitionExtra[] keyPhraseExtras;
+
+        /** Additional data available for each recognized key phrases in the model */
+        public final boolean keyPhraseInCapture;
+
+        KeyPhraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
+               int captureSession, int captureDelayMs, byte[] data,
+               boolean keyPhraseInCapture, KeyPhraseRecognitionExtra[] keyPhraseExtras) {
+            super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs, data);
+            this.keyPhraseInCapture = keyPhraseInCapture;
+            this.keyPhraseExtras = keyPhraseExtras;
+        }
+    }
+
+    /**
+     * Returns a list of descriptors for all harware modules loaded.
+     * @param modules A ModuleProperties array where the list will be returned.
+     * @return - {@link #STATUS_OK} in case of success
+     *         - {@link #STATUS_ERROR} in case of unspecified error
+     *         - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission
+     *         - {@link #STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link #STATUS_BAD_VALUE} if modules is null
+     *         - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails
+     */
+    public static native int listModules(ArrayList <ModuleProperties> modules);
+
+    /**
+     * Get an interface on a hardware module to control sound models and recognition on
+     * this module.
+     * @param moduleId Sound module system identifier {@link ModuleProperties#id}. mandatory.
+     * @param listener {@link StatusListener} interface. Mandatory.
+     * @param handler the Handler that will receive the callabcks. Can be null if default handler
+     *                is OK.
+     * @return a valid sound module in case of success or null in case of error.
+     */
+    public static SoundTriggerModule attachModule(int moduleId,
+                                                  StatusListener listener,
+                                                  Handler handler) {
+        if (listener == null) {
+            return null;
+        }
+        SoundTriggerModule module = new SoundTriggerModule(moduleId, listener, handler);
+        return module;
+    }
+
+    /**
+     * Interface provided by the client application when attaching to a {@link SoundTriggerModule}
+     * to received recognition and error notifications.
+     */
+    public static interface StatusListener {
+        /**
+         * Called when recognition succeeds of fails
+         */
+        public abstract void onRecognition(RecognitionEvent event);
+
+        /**
+         * Called when the sound trigger native service dies
+         */
+        public abstract void onServiceDied();
+    }
+}
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
new file mode 100644
index 0000000..776f85d
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.soundtrigger;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import java.lang.ref.WeakReference;
+import java.util.UUID;
+
+/**
+ * The SoundTriggerModule provides APIs to control sound models and sound detection
+ * on a given sound trigger hardware module.
+ *
+ * @hide
+ */
+public class SoundTriggerModule {
+    private long mNativeContext;
+
+    private int mId;
+    private NativeEventHandlerDelegate mEventHandlerDelegate;
+
+    private static final int EVENT_RECOGNITION = 1;
+    private static final int EVENT_SERVICE_DIED = 2;
+
+    SoundTriggerModule(int moduleId, SoundTrigger.StatusListener listener, Handler handler) {
+        mId = moduleId;
+        mEventHandlerDelegate = new NativeEventHandlerDelegate(listener, handler);
+        native_setup(new WeakReference<SoundTriggerModule>(this));
+    }
+    private native void native_setup(Object module_this);
+
+    @Override
+    protected void finalize() {
+        native_finalize();
+    }
+    private native void native_finalize();
+
+    /**
+     * Detach from this module. The {@link SoundTrigger.StatusListener} callback will not be called
+     * anymore and associated resources will be released.
+     * */
+    public native void detach();
+
+    /**
+     * Load a {@link SoundTrigger.SoundModel} to the hardware. A sound model must be loaded in
+     * order to start listening to a key phrase in this model.
+     * @param model The sound model to load.
+     * @param soundModelHandle an array of int where the sound model handle will be returned.
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
+     *         - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have
+     *         system permission
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} if parameters are invalid
+     *         - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native
+     *         service fails
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
+     */
+    public native int loadSoundModel(SoundTrigger.SoundModel model, int[] soundModelHandle);
+
+    /**
+     * Unload a {@link SoundTrigger.SoundModel} and abort any pendiong recognition
+     * @param soundModelHandle The sound model handle
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
+     *         - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have
+     *         system permission
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid
+     *         - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native
+     *         service fails
+     */
+    public native int unloadSoundModel(int soundModelHandle);
+
+    /**
+     * Start listening to all key phrases in a {@link SoundTrigger.SoundModel}.
+     * Recognition must be restarted after each callback (success or failure) received on
+     * the {@link SoundTrigger.StatusListener}.
+     * @param soundModelHandle The sound model handle to start listening to
+     * @param data Opaque data for use by the implementation for this recognition
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
+     *         - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have
+     *         system permission
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid
+     *         - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native
+     *         service fails
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
+     */
+    public native int startRecognition(int soundModelHandle, byte[] data);
+
+    /**
+     * Stop listening to all key phrases in a {@link SoundTrigger.SoundModel}
+     * @param soundModelHandle The sound model handle to stop listening to
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
+     *         - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have
+     *         system permission
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid
+     *         - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native
+     *         service fails
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
+     */
+    public native int stopRecognition(int soundModelHandle);
+
+    private class NativeEventHandlerDelegate {
+        private final Handler mHandler;
+
+        NativeEventHandlerDelegate(final SoundTrigger.StatusListener listener,
+                                   Handler handler) {
+            // find the looper for our new event handler
+            Looper looper;
+            if (handler != null) {
+                looper = handler.getLooper();
+            } else {
+                looper = Looper.myLooper();
+                if (looper == null) {
+                    looper = Looper.getMainLooper();
+                }
+            }
+
+            // construct the event handler with this looper
+            if (looper != null) {
+                // implement the event handler delegate
+                mHandler = new Handler(looper) {
+                    @Override
+                    public void handleMessage(Message msg) {
+                        switch(msg.what) {
+                        case EVENT_RECOGNITION:
+                            if (listener != null) {
+                                listener.onRecognition(
+                                        (SoundTrigger.RecognitionEvent)msg.obj);
+                            }
+                            break;
+                        case EVENT_SERVICE_DIED:
+                            if (listener != null) {
+                                listener.onServiceDied();
+                            }
+                            break;
+                        default:
+                            break;
+                        }
+                    }
+                };
+            } else {
+                mHandler = null;
+            }
+        }
+
+        Handler handler() {
+            return mHandler;
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private static void postEventFromNative(Object module_ref,
+                                            int what, int arg1, int arg2, Object obj) {
+        SoundTriggerModule module = (SoundTriggerModule)((WeakReference)module_ref).get();
+        if (module == null) {
+            return;
+        }
+
+        NativeEventHandlerDelegate delegate = module.mEventHandlerDelegate;
+        if (delegate != null) {
+            Handler handler = delegate.handler();
+            if (handler != null) {
+                Message m = handler.obtainMessage(what, arg1, arg2, obj);
+                handler.sendMessage(m);
+            }
+        }
+    }
+}
+
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 65d4726..ff90e789 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -898,6 +898,7 @@
                 case NetworkCapabilities.NET_CAPABILITY_IMS:
                 case NetworkCapabilities.NET_CAPABILITY_RCS:
                 case NetworkCapabilities.NET_CAPABILITY_XCAP:
+                case NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED: //there by default
                     continue;
                 default:
                     // At least one capability usually provided by unrestricted
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 8eefa0f..bb05936 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -44,7 +44,7 @@
  * does not affect live networks.
  *
  */
-public final class LinkProperties implements Parcelable {
+public class LinkProperties implements Parcelable {
     // The interface described by the network link.
     private String mIfaceName;
     private ArrayList<LinkAddress> mLinkAddresses = new ArrayList<LinkAddress>();
@@ -463,6 +463,7 @@
 
     /**
      * Implement the Parcelable interface
+     * @hide
      */
     public int describeContents() {
         return 0;
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 7e8b1f1..3d0874b 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -80,6 +80,11 @@
      */
     public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3;
 
+    /* centralize place where base network score, and network score scaling, will be
+     * stored, so as we can consistently compare apple and oranges, or wifi, ethernet and LTE
+     */
+    public static final int WIFI_BASE_SCORE = 60;
+
     /**
      * Sent by the NetworkAgent to ConnectivityService to pass the current
      * network score.
diff --git a/core/java/android/net/ProxyDataTracker.java b/core/java/android/net/ProxyDataTracker.java
index 573a8f8..a578383 100644
--- a/core/java/android/net/ProxyDataTracker.java
+++ b/core/java/android/net/ProxyDataTracker.java
@@ -48,6 +48,7 @@
     // TODO: investigate how to get these DNS addresses from the system.
     private static final String DNS1 = "8.8.8.8";
     private static final String DNS2 = "8.8.4.4";
+    private static final String INTERFACE_NAME = "ifb0";
     private static final String REASON_ENABLED = "enabled";
     private static final String REASON_DISABLED = "disabled";
     private static final String REASON_PROXY_DOWN = "proxy_down";
@@ -107,10 +108,11 @@
         mNetworkCapabilities = new NetworkCapabilities();
         mNetworkInfo.setIsAvailable(true);
         try {
-          mLinkProperties.addDnsServer(InetAddress.getByName(DNS1));
-          mLinkProperties.addDnsServer(InetAddress.getByName(DNS2));
+            mLinkProperties.addDnsServer(InetAddress.getByName(DNS1));
+            mLinkProperties.addDnsServer(InetAddress.getByName(DNS2));
+            mLinkProperties.setInterfaceName(INTERFACE_NAME);
         } catch (UnknownHostException e) {
-          Log.e(TAG, "Could not add DNS address", e);
+            Log.e(TAG, "Could not add DNS address", e);
         }
     }
 
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index c2b888c..8b42bcd 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -46,7 +46,7 @@
  * destination and gateway are both specified, they must be of the same address family
  * (IPv4 or IPv6).
  */
-public final class RouteInfo implements Parcelable {
+public class RouteInfo implements Parcelable {
     /**
      * The IP destination address for this route.
      * TODO: Make this an IpPrefix.
@@ -378,6 +378,7 @@
 
     /**
      * Implement the Parcelable interface
+     * @hide
      */
     public int describeContents() {
         return 0;
@@ -385,6 +386,7 @@
 
     /**
      * Implement the Parcelable interface
+     * @hide
      */
     public void writeToParcel(Parcel dest, int flags) {
         if (mDestination == null) {
@@ -407,6 +409,7 @@
 
     /**
      * Implement the Parcelable interface.
+     * @hide
      */
     public static final Creator<RouteInfo> CREATOR =
         new Creator<RouteInfo>() {
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index e84b695..975bfc2 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -292,6 +292,17 @@
     }
 
     /**
+     * Returns the config directory for a user. This is for use by system services to store files
+     * relating to the user which should be readable by any app running as that user.
+     *
+     * @hide
+     */
+    public static File getUserConfigDirectory(int userId) {
+        return new File(new File(new File(
+                getDataDirectory(), "misc"), "user"), Integer.toString(userId));
+    }
+
+    /**
      * Returns whether the Encrypted File System feature is enabled on the device or not.
      * @return <code>true</code> if Encrypted File System feature is enabled, <code>false</code>
      * if disabled.
diff --git a/core/java/android/os/FileBridge.java b/core/java/android/os/FileBridge.java
new file mode 100644
index 0000000..7f8bc9f
--- /dev/null
+++ b/core/java/android/os/FileBridge.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.SOCK_STREAM;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import libcore.io.IoBridge;
+import libcore.io.IoUtils;
+import libcore.io.Memory;
+import libcore.io.Streams;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.SyncFailedException;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * Simple bridge that allows file access across process boundaries without
+ * returning the underlying {@link FileDescriptor}. This is useful when the
+ * server side needs to strongly assert that a client side is completely
+ * hands-off.
+ *
+ * @hide
+ */
+public class FileBridge extends Thread {
+    private static final String TAG = "FileBridge";
+
+    // TODO: consider extending to support bidirectional IO
+
+    private static final int MSG_LENGTH = 8;
+
+    /** CMD_WRITE [len] [data] */
+    private static final int CMD_WRITE = 1;
+    /** CMD_FSYNC */
+    private static final int CMD_FSYNC = 2;
+
+    private FileDescriptor mTarget;
+
+    private final FileDescriptor mServer = new FileDescriptor();
+    private final FileDescriptor mClient = new FileDescriptor();
+
+    private volatile boolean mClosed;
+
+    public FileBridge() {
+        try {
+            Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mServer, mClient);
+        } catch (ErrnoException e) {
+            throw new RuntimeException("Failed to create bridge");
+        }
+    }
+
+    public boolean isClosed() {
+        return mClosed;
+    }
+
+    public void setTargetFile(FileDescriptor target) {
+        mTarget = target;
+    }
+
+    public FileDescriptor getClientSocket() {
+        return mClient;
+    }
+
+    @Override
+    public void run() {
+        final byte[] temp = new byte[8192];
+        try {
+            while (IoBridge.read(mServer, temp, 0, MSG_LENGTH) == MSG_LENGTH) {
+                final int cmd = Memory.peekInt(temp, 0, ByteOrder.BIG_ENDIAN);
+
+                if (cmd == CMD_WRITE) {
+                    // Shuttle data into local file
+                    int len = Memory.peekInt(temp, 4, ByteOrder.BIG_ENDIAN);
+                    while (len > 0) {
+                        int n = IoBridge.read(mServer, temp, 0, Math.min(temp.length, len));
+                        IoBridge.write(mTarget, temp, 0, n);
+                        len -= n;
+                    }
+
+                } else if (cmd == CMD_FSYNC) {
+                    // Sync and echo back to confirm
+                    Os.fsync(mTarget);
+                    IoBridge.write(mServer, temp, 0, MSG_LENGTH);
+                }
+            }
+
+            // Client was closed; one last fsync
+            Os.fsync(mTarget);
+
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Failed during bridge: ", e);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed during bridge: ", e);
+        } finally {
+            IoUtils.closeQuietly(mTarget);
+            IoUtils.closeQuietly(mServer);
+            IoUtils.closeQuietly(mClient);
+            mClosed = true;
+        }
+    }
+
+    public static class FileBridgeOutputStream extends OutputStream {
+        private final FileDescriptor mClient;
+        private final byte[] mTemp = new byte[MSG_LENGTH];
+
+        public FileBridgeOutputStream(FileDescriptor client) {
+            mClient = client;
+        }
+
+        @Override
+        public void close() throws IOException {
+            IoBridge.closeAndSignalBlockedThreads(mClient);
+        }
+
+        @Override
+        public void flush() throws IOException {
+            Memory.pokeInt(mTemp, 0, CMD_FSYNC, ByteOrder.BIG_ENDIAN);
+            IoBridge.write(mClient, mTemp, 0, MSG_LENGTH);
+
+            // Wait for server to ack
+            if (IoBridge.read(mClient, mTemp, 0, MSG_LENGTH) == MSG_LENGTH) {
+                if (Memory.peekInt(mTemp, 0, ByteOrder.BIG_ENDIAN) == CMD_FSYNC) {
+                    return;
+                }
+            }
+
+            throw new SyncFailedException("Failed to fsync() across bridge");
+        }
+
+        @Override
+        public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+            Arrays.checkOffsetAndCount(buffer.length, byteOffset, byteCount);
+            Memory.pokeInt(mTemp, 0, CMD_WRITE, ByteOrder.BIG_ENDIAN);
+            Memory.pokeInt(mTemp, 4, byteCount, ByteOrder.BIG_ENDIAN);
+            IoBridge.write(mClient, mTemp, 0, MSG_LENGTH);
+            IoBridge.write(mClient, buffer, byteOffset, byteCount);
+        }
+
+        @Override
+        public void write(int oneByte) throws IOException {
+            Streams.writeSingleByte(this, oneByte);
+        }
+    }
+}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index cd47099..e77ef95 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -39,9 +39,6 @@
     UserInfo getProfileParent(int userHandle);
     UserInfo getUserInfo(int userHandle);
     boolean isRestricted();
-    void setGuestEnabled(boolean enable);
-    boolean isGuestEnabled();
-    void wipeUser(int userHandle);
     int getUserSerialNumber(int userHandle);
     int getUserHandle(int userSerialNumber);
     Bundle getUserRestrictions(int userHandle);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index cd1cd30..92e80a5 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -314,6 +314,11 @@
      * The value to pass as the 'reason' argument to reboot() to
      * reboot into recovery mode (for applying system updates, doing
      * factory resets, etc.).
+     * <p>
+     * Requires the {@link android.Manifest.permission#RECOVERY}
+     * permission (in addition to
+     * {@link android.Manifest.permission#REBOOT}).
+     * </p>
      */
     public static final String REBOOT_RECOVERY = "recovery";
     
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 112ec1d..86c749a 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -156,6 +156,12 @@
     public static final int LAST_ISOLATED_UID = 99999;
 
     /**
+     * Defines the gid shared by all applications running under the same profile.
+     * @hide
+     */
+    public static final int SHARED_USER_GID = 9997;
+
+    /**
      * First gid for applications to share resources. Used when forward-locking
      * is enabled but all UserHandles need to be able to read the resources.
      * @hide
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 57ed979..474192fd 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -70,6 +70,8 @@
     public static final long TRACE_TAG_DALVIK = 1L << 14;
     /** @hide */
     public static final long TRACE_TAG_RS = 1L << 15;
+    /** @hide */
+    public static final long TRACE_TAG_BIONIC = 1L << 16;
 
     private static final long TRACE_TAG_NOT_READY = 1L << 63;
     private static final int MAX_SECTION_NAME_LEN = 127;
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 6e693a4..914c170 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -145,6 +145,14 @@
     }
 
     /**
+     * Returns the gid shared between all apps with this userId.
+     * @hide
+     */
+    public static final int getUserGid(int userId) {
+        return getUid(userId, Process.SHARED_USER_GID);
+    }
+
+    /**
      * Returns the shared app gid for a given uid or appId.
      * @hide
      */
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f7a89ba..eb3c748 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -25,6 +25,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.provider.Settings;
 import android.util.Log;
 
 import com.android.internal.R;
@@ -361,6 +362,16 @@
     }
 
     /**
+     * Checks if the calling app is running as a guest user.
+     * @return whether the caller is a guest user.
+     * @hide
+     */
+    public boolean isGuestUser() {
+        UserInfo user = getUserInfo(UserHandle.myUserId());
+        return user != null ? user.isGuest() : false;
+    }
+
+    /**
      * Return whether the given user is actively running.  This means that
      * the user is in the "started" state, not "stopped" -- it is currently
      * allowed to run code through scheduled alarms, receiving broadcasts,
@@ -481,10 +492,11 @@
     }
 
     /**
-     * @hide
      * Returns whether the current user has been disallowed from performing certain actions
      * or setting certain settings.
-     * @param restrictionKey the string key representing the restriction
+     *
+     * @param restrictionKey The string key representing the restriction.
+     * @return {@code true} if the current user has the given restriction, {@code false} otherwise.
      */
     public boolean hasUserRestriction(String restrictionKey) {
         return hasUserRestriction(restrictionKey, Process.myUserHandle());
@@ -549,6 +561,21 @@
     }
 
     /**
+     * Creates a guest user and configures it.
+     * @param context an application context
+     * @param name the name to set for the user
+     * @hide
+     */
+    public UserInfo createGuest(Context context, String name) {
+        UserInfo guest = createUser(name, UserInfo.FLAG_GUEST);
+        if (guest != null) {
+            Settings.Secure.putStringForUser(context.getContentResolver(),
+                    Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
+        }
+        return guest;
+    }
+
+    /**
      * Creates a user with the specified name and options as a profile of another user.
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
      *
@@ -826,50 +853,6 @@
     }
 
     /**
-     * Enable or disable the use of a guest account. If disabled, the existing guest account
-     * will be wiped.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
-     * @param enable whether to enable a guest account.
-     * @hide
-     */
-    public void setGuestEnabled(boolean enable) {
-        try {
-            mService.setGuestEnabled(enable);
-        } catch (RemoteException re) {
-            Log.w(TAG, "Could not change guest account availability to " + enable);
-        }
-    }
-
-    /**
-     * Checks if a guest user is enabled for this device.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
-     * @return whether a guest user is enabled
-     * @hide
-     */
-    public boolean isGuestEnabled() {
-        try {
-            return mService.isGuestEnabled();
-        } catch (RemoteException re) {
-            Log.w(TAG, "Could not retrieve guest enabled state");
-            return false;
-        }
-    }
-
-    /**
-     * Wipes all the data for a user, but doesn't remove the user.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
-     * @param userHandle
-     * @hide
-     */
-    public void wipeUser(int userHandle) {
-        try {
-            mService.wipeUser(userHandle);
-        } catch (RemoteException re) {
-            Log.w(TAG, "Could not wipe user " + userHandle);
-        }
-    }
-
-    /**
      * Returns the maximum number of users that can be created on this device. A return value
      * of 1 means that it is a single user device.
      * @hide
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index cb0f142..c1d4d4c 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -74,7 +74,6 @@
      * @param streamHint An {@link AudioManager} stream type corresponding to the vibration type.
      *        For example, specify {@link AudioManager#STREAM_ALARM} for alarm vibrations or
      *        {@link AudioManager#STREAM_RING} for vibrations associated with incoming calls.
-     * @hide
      */
     public void vibrate(long milliseconds, int streamHint) {
         vibrate(Process.myUid(), mPackageName, milliseconds, streamHint);
@@ -126,7 +125,6 @@
      * @param streamHint An {@link AudioManager} stream type corresponding to the vibration type.
      *        For example, specify {@link AudioManager#STREAM_ALARM} for alarm vibrations or
      *        {@link AudioManager#STREAM_RING} for vibrations associated with incoming calls.
-     * @hide
      */
     public void vibrate(long[] pattern, int repeat, int streamHint) {
         vibrate(Process.myUid(), mPackageName, pattern, repeat, streamHint);
diff --git a/core/java/android/print/ILayoutResultCallback.aidl b/core/java/android/print/ILayoutResultCallback.aidl
index 43b8c30..68c1dac 100644
--- a/core/java/android/print/ILayoutResultCallback.aidl
+++ b/core/java/android/print/ILayoutResultCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.print;
 
+import android.os.ICancellationSignal;
 import android.print.PrintDocumentInfo;
 
 /**
@@ -24,6 +25,8 @@
  * @hide
  */
 oneway interface ILayoutResultCallback {
+    void onLayoutStarted(ICancellationSignal cancellation, int sequence);
     void onLayoutFinished(in PrintDocumentInfo info, boolean changed, int sequence);
     void onLayoutFailed(CharSequence error, int sequence);
+    void onLayoutCanceled(int sequence);
 }
diff --git a/core/java/android/print/IPrintDocumentAdapter.aidl b/core/java/android/print/IPrintDocumentAdapter.aidl
index 2b95c12..9d384fb 100644
--- a/core/java/android/print/IPrintDocumentAdapter.aidl
+++ b/core/java/android/print/IPrintDocumentAdapter.aidl
@@ -37,5 +37,4 @@
     void write(in PageRange[] pages, in ParcelFileDescriptor fd,
             IWriteResultCallback callback, int sequence);
     void finish();
-    void cancel();
 }
diff --git a/core/java/android/print/IWriteResultCallback.aidl b/core/java/android/print/IWriteResultCallback.aidl
index 8281c4e..8fb33e1 100644
--- a/core/java/android/print/IWriteResultCallback.aidl
+++ b/core/java/android/print/IWriteResultCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.print;
 
+import android.os.ICancellationSignal;
 import android.print.PageRange;
 
 /**
@@ -24,6 +25,8 @@
  * @hide
  */
 oneway interface IWriteResultCallback {
+    void onWriteStarted(ICancellationSignal cancellation, int sequence);
     void onWriteFinished(in PageRange[] pages, int sequence);
     void onWriteFailed(CharSequence error, int sequence);
+    void onWriteCanceled(int sequence);
 }
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index c6254e0..2810d55 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -151,6 +151,105 @@
         mColorMode = colorMode;
     }
 
+    /**
+     * Gets whether this print attributes are in portrait orientation,
+     * which is the media size is in portrait and all orientation dependent
+     * attributes such as resolution and margins are properly adjusted.
+     *
+     * @return Whether this print attributes are in portrait.
+     *
+     * @hide
+     */
+    public boolean isPortrait() {
+        return mMediaSize.isPortrait();
+    }
+
+    /**
+     * Gets a new print attributes instance which is in portrait orientation,
+     * which is the media size is in portrait and all orientation dependent
+     * attributes such as resolution and margins are properly adjusted.
+     *
+     * @return New instance in portrait orientation if this one is in
+     * landscape, otherwise this instance.
+     *
+     * @hide
+     */
+    public PrintAttributes asPortrait() {
+        if (isPortrait()) {
+            return this;
+        }
+
+        PrintAttributes attributes = new PrintAttributes();
+
+        // Rotate the media size.
+        attributes.setMediaSize(getMediaSize().asPortrait());
+
+        // Rotate the resolution.
+        Resolution oldResolution = getResolution();
+        Resolution newResolution = new Resolution(
+                oldResolution.getId(),
+                oldResolution.getLabel(),
+                oldResolution.getVerticalDpi(),
+                oldResolution.getHorizontalDpi());
+        attributes.setResolution(newResolution);
+
+        // Rotate the physical margins.
+        Margins oldMinMargins = getMinMargins();
+        Margins newMinMargins = new Margins(
+                oldMinMargins.getBottomMils(),
+                oldMinMargins.getLeftMils(),
+                oldMinMargins.getTopMils(),
+                oldMinMargins.getRightMils());
+        attributes.setMinMargins(newMinMargins);
+
+        attributes.setColorMode(getColorMode());
+
+        return attributes;
+    }
+
+    /**
+     * Gets a new print attributes instance which is in landscape orientation,
+     * which is the media size is in landscape and all orientation dependent
+     * attributes such as resolution and margins are properly adjusted.
+     *
+     * @return New instance in landscape orientation if this one is in
+     * portrait, otherwise this instance.
+     *
+     * @hide
+     */
+    public PrintAttributes asLandscape() {
+        if (!isPortrait()) {
+            return this;
+        }
+
+        PrintAttributes attributes = new PrintAttributes();
+
+        // Rotate the media size.
+        attributes.setMediaSize(getMediaSize().asLandscape());
+
+        // Rotate the resolution.
+        Resolution oldResolution = getResolution();
+        Resolution newResolution = new Resolution(
+                oldResolution.getId(),
+                oldResolution.getLabel(),
+                oldResolution.getVerticalDpi(),
+                oldResolution.getHorizontalDpi());
+        attributes.setResolution(newResolution);
+
+        // Rotate the physical margins.
+        Margins oldMinMargins = getMinMargins();
+        Margins newMargins = new Margins(
+                oldMinMargins.getTopMils(),
+                oldMinMargins.getRightMils(),
+                oldMinMargins.getBottomMils(),
+                oldMinMargins.getLeftMils());
+        attributes.setMinMargins(newMargins);
+
+        attributes.setColorMode(getColorMode());
+
+        return attributes;
+    }
+
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         if (mMediaSize != null) {
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index 811751d..9361286 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -24,6 +24,7 @@
 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;
@@ -41,6 +42,7 @@
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -50,12 +52,12 @@
  * <p>
  * To obtain a handle to the print manager do the following:
  * </p>
- * 
+ *
  * <pre>
  * PrintManager printManager =
  *         (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
  * </pre>
- * 
+ *
  * <h3>Print mechanics</h3>
  * <p>
  * The key idea behind printing on the platform is that the content to be printed
@@ -344,7 +346,7 @@
         try {
             mService.cancelPrintJob(printJobId, mAppId, mUserId);
         } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re);
+            Log.e(LOG_TAG, "Error canceling a print job: " + printJobId, re);
         }
     }
 
@@ -505,30 +507,17 @@
 
     private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub
             implements ActivityLifecycleCallbacks {
-
         private final Object mLock = new Object();
 
-        private CancellationSignal mLayoutOrWriteCancellation;
+        private Activity mActivity; // Strong reference OK - cleared in destroy
 
-        private Activity mActivity; // Strong reference OK - cleared in finish()
+        private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in destroy
 
-        private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish
+        private Handler mHandler; // Strong reference OK - cleared in destroy
 
-        private Handler mHandler; // Strong reference OK - cleared in finish()
+        private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in destroy
 
-        private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in finish
-
-        private LayoutSpec mLastLayoutSpec;
-
-        private WriteSpec mLastWriteSpec;
-
-        private boolean mStartReqeusted;
-        private boolean mStarted;
-
-        private boolean mFinishRequested;
-        private boolean mFinished;
-
-        private boolean mDestroyed;
+        private DestroyableCallback mPendingCallback;
 
         public PrintDocumentAdapterDelegate(Activity activity,
                 PrintDocumentAdapter documentAdapter) {
@@ -542,11 +531,10 @@
         public void setObserver(IPrintDocumentAdapterObserver observer) {
             final boolean destroyed;
             synchronized (mLock) {
-                if (!mDestroyed) {
-                    mObserver = observer;
-                }
-                destroyed = mDestroyed;
+                mObserver = observer;
+                destroyed = isDestroyedLocked();
             }
+
             if (destroyed) {
                 try {
                     observer.onDestroy();
@@ -559,126 +547,89 @@
         @Override
         public void start() {
             synchronized (mLock) {
-                // Started called or finish called or destroyed - nothing to do.
-                if (mStartReqeusted || mFinishRequested || mDestroyed) {
-                    return;
+                // If destroyed the handler is null.
+                if (!isDestroyedLocked()) {
+                    mHandler.obtainMessage(MyHandler.MSG_ON_START,
+                            mDocumentAdapter).sendToTarget();
                 }
-
-                mStartReqeusted = true;
-
-                doPendingWorkLocked();
             }
         }
 
         @Override
         public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
                 ILayoutResultCallback callback, Bundle metadata, int sequence) {
-            final boolean destroyed;
-            synchronized (mLock) {
-                destroyed = mDestroyed;
-                // If start called and not finished called and not destroyed - do some work.
-                if (mStartReqeusted && !mFinishRequested && !mDestroyed) {
-                    // Layout cancels write and overrides layout.
-                    if (mLastWriteSpec != null) {
-                        IoUtils.closeQuietly(mLastWriteSpec.fd);
-                        mLastWriteSpec = null;
-                    }
 
-                    mLastLayoutSpec = new LayoutSpec();
-                    mLastLayoutSpec.callback = callback;
-                    mLastLayoutSpec.oldAttributes = oldAttributes;
-                    mLastLayoutSpec.newAttributes = newAttributes;
-                    mLastLayoutSpec.metadata = metadata;
-                    mLastLayoutSpec.sequence = sequence;
-
-                    // Cancel the previous cancellable operation.When the
-                    // cancellation completes we will do the pending work.
-                    if (cancelPreviousCancellableOperationLocked()) {
-                        return;
-                    }
-
-                    doPendingWorkLocked();
-                }
+            ICancellationSignal cancellationTransport = CancellationSignal.createTransport();
+            try {
+                callback.onLayoutStarted(cancellationTransport, sequence);
+            } catch (RemoteException re) {
+                // The spooler is dead - can't recover.
+                Log.e(LOG_TAG, "Error notifying for layout start", re);
+                return;
             }
-            if (destroyed) {
-                try {
-                    callback.onLayoutFailed(null, sequence);
-                } catch (RemoteException re) {
-                    Log.i(LOG_TAG, "Error notifying for cancelled layout", re);
+
+            synchronized (mLock) {
+                // If destroyed the handler is null.
+                if (isDestroyedLocked()) {
+                    return;
                 }
+
+                CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
+                        cancellationTransport);
+
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = mDocumentAdapter;
+                args.arg2 = oldAttributes;
+                args.arg3 = newAttributes;
+                args.arg4 = cancellationSignal;
+                args.arg5 = new MyLayoutResultCallback(callback, sequence);
+                args.arg6 = metadata;
+
+                mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT, args).sendToTarget();
             }
         }
 
         @Override
         public void write(PageRange[] pages, ParcelFileDescriptor fd,
                 IWriteResultCallback callback, int sequence) {
-            final boolean destroyed;
-            synchronized (mLock) {
-                destroyed = mDestroyed;
-                // If start called and not finished called and not destroyed - do some work.
-                if (mStartReqeusted && !mFinishRequested && !mDestroyed) {
-                    // Write cancels previous writes.
-                    if (mLastWriteSpec != null) {
-                        IoUtils.closeQuietly(mLastWriteSpec.fd);
-                        mLastWriteSpec = null;
-                    }
 
-                    mLastWriteSpec = new WriteSpec();
-                    mLastWriteSpec.callback = callback;
-                    mLastWriteSpec.pages = pages;
-                    mLastWriteSpec.fd = fd;
-                    mLastWriteSpec.sequence = sequence;
-
-                    // Cancel the previous cancellable operation.When the
-                    // cancellation completes we will do the pending work.
-                    if (cancelPreviousCancellableOperationLocked()) {
-                        return;
-                    }
-
-                    doPendingWorkLocked();
-                }
+            ICancellationSignal cancellationTransport = CancellationSignal.createTransport();
+            try {
+                callback.onWriteStarted(cancellationTransport, sequence);
+            } catch (RemoteException re) {
+                // The spooler is dead - can't recover.
+                Log.e(LOG_TAG, "Error notifying for write start", re);
+                return;
             }
-            if (destroyed) {
-                try {
-                    callback.onWriteFailed(null, sequence);
-                } catch (RemoteException re) {
-                    Log.i(LOG_TAG, "Error notifying for cancelled write", re);
+
+            synchronized (mLock) {
+                // If destroyed the handler is null.
+                if (isDestroyedLocked()) {
+                    return;
                 }
+
+                CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
+                        cancellationTransport);
+
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = mDocumentAdapter;
+                args.arg2 = pages;
+                args.arg3 = fd;
+                args.arg4 = cancellationSignal;
+                args.arg5 = new MyWriteResultCallback(callback, fd, sequence);
+
+                mHandler.obtainMessage(MyHandler.MSG_ON_WRITE, args).sendToTarget();
             }
         }
 
         @Override
         public void finish() {
             synchronized (mLock) {
-                // Start not called or finish called or destroyed - nothing to do.
-                if (!mStartReqeusted || mFinishRequested || mDestroyed) {
-                    return;
+                // If destroyed the handler is null.
+                if (!isDestroyedLocked()) {
+                    mHandler.obtainMessage(MyHandler.MSG_ON_FINISH,
+                            mDocumentAdapter).sendToTarget();
                 }
-
-                mFinishRequested = true;
-
-                // When the current write or layout complete we
-                // will do the pending work.
-                if (mLastLayoutSpec != null || mLastWriteSpec != null) {
-                    if (DEBUG) {
-                        Log.i(LOG_TAG, "Waiting for current operation");
-                    }
-                    return;
-                }
-
-                doPendingWorkLocked();
-            }
-        }
-
-        @Override
-        public void cancel() {
-            // Start not called or finish called or destroyed - nothing to do.
-            if (!mStartReqeusted || mFinishRequested || mDestroyed) {
-                return;
-            }
-            // Request cancellation of pending work if needed.
-            synchronized (mLock) {
-                cancelPreviousCancellableOperationLocked();
             }
         }
 
@@ -719,20 +670,14 @@
             // Note the the spooler has a death recipient that observes if
             // this process gets killed so we cover the case of onDestroy not
             // being called due to this process being killed to reclaim memory.
-            final IPrintDocumentAdapterObserver observer;
+            IPrintDocumentAdapterObserver observer = null;
             synchronized (mLock) {
                 if (activity == mActivity) {
-                    mDestroyed = true;
                     observer = mObserver;
-                    clearLocked();
-                } else {
-                    observer = null;
-                    activity = null;
+                    destroyLocked();
                 }
             }
             if (observer != null) {
-                activity.getApplication().unregisterActivityLifecycleCallbacks(
-                        PrintDocumentAdapterDelegate.this);
                 try {
                     observer.onDestroy();
                 } catch (RemoteException re) {
@@ -741,67 +686,39 @@
             }
         }
 
-        private boolean isFinished() {
-            return mDocumentAdapter == null;
+        private boolean isDestroyedLocked() {
+            return (mActivity == null);
         }
 
-        private void clearLocked() {
+        private void destroyLocked() {
+            mActivity.getApplication().unregisterActivityLifecycleCallbacks(
+                    PrintDocumentAdapterDelegate.this);
             mActivity = null;
+
             mDocumentAdapter = null;
+
+            // This method is only called from the main thread, so
+            // clearing the messages guarantees that any time a
+            // message is handled we are not in a destroyed state.
+            mHandler.removeMessages(MyHandler.MSG_ON_START);
+            mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT);
+            mHandler.removeMessages(MyHandler.MSG_ON_WRITE);
+            mHandler.removeMessages(MyHandler.MSG_ON_FINISH);
             mHandler = null;
-            mLayoutOrWriteCancellation = null;
-            mLastLayoutSpec = null;
-            if (mLastWriteSpec != null) {
-                IoUtils.closeQuietly(mLastWriteSpec.fd);
-                mLastWriteSpec = null;
+
+            mObserver = null;
+
+            if (mPendingCallback != null) {
+                mPendingCallback.destroy();
+                mPendingCallback = null;
             }
         }
 
-        private boolean cancelPreviousCancellableOperationLocked() {
-            if (mLayoutOrWriteCancellation != null) {
-                mLayoutOrWriteCancellation.cancel();
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "Cancelling previous operation");
-                }
-                return true;
-            }
-            return false;
-        }
-
-        private void doPendingWorkLocked() {
-            if (mStartReqeusted && !mStarted) {
-                mStarted = true;
-                mHandler.sendEmptyMessage(MyHandler.MSG_START);
-            } else if (mLastLayoutSpec != null) {
-                mHandler.sendEmptyMessage(MyHandler.MSG_LAYOUT);
-            } else if (mLastWriteSpec != null) {
-                mHandler.sendEmptyMessage(MyHandler.MSG_WRITE);
-            } else if (mFinishRequested && !mFinished) {
-                mFinished = true;
-                mHandler.sendEmptyMessage(MyHandler.MSG_FINISH);
-            }
-        }
-
-        private class LayoutSpec {
-            ILayoutResultCallback callback;
-            PrintAttributes oldAttributes;
-            PrintAttributes newAttributes;
-            Bundle metadata;
-            int sequence;
-        }
-
-        private class WriteSpec {
-            IWriteResultCallback callback;
-            PageRange[] pages;
-            ParcelFileDescriptor fd;
-            int sequence;
-        }
-
         private final class MyHandler extends Handler {
-            public static final int MSG_START = 1;
-            public static final int MSG_LAYOUT = 2;
-            public static final int MSG_WRITE = 3;
-            public static final int MSG_FINISH = 4;
+            public static final int MSG_ON_START = 1;
+            public static final int MSG_ON_LAYOUT = 2;
+            public static final int MSG_ON_WRITE = 3;
+            public static final int MSG_ON_FINISH = 4;
 
             public MyHandler(Looper looper) {
                 super(looper, null, true);
@@ -809,84 +726,71 @@
 
             @Override
             public void handleMessage(Message message) {
-                if (isFinished()) {
-                    return;
-                }
                 switch (message.what) {
-                    case MSG_START: {
-                        final PrintDocumentAdapter adapter;
-                        synchronized (mLock) {
-                            adapter = mDocumentAdapter;
-                        }
-                        if (adapter != null) {
-                            adapter.onStart();
-                        }
-                    } break;
-
-                    case MSG_LAYOUT: {
-                        final PrintDocumentAdapter adapter;
-                        final CancellationSignal cancellation;
-                        final LayoutSpec layoutSpec;
-
-                        synchronized (mLock) {
-                            adapter = mDocumentAdapter;
-                            layoutSpec = mLastLayoutSpec;
-                            mLastLayoutSpec = null;
-                            cancellation = new CancellationSignal();
-                            mLayoutOrWriteCancellation = cancellation;
-                        }
-
-                        if (layoutSpec != null && adapter != null) {
-                            if (DEBUG) {
-                                Log.i(LOG_TAG, "Performing layout");
-                            }
-                            adapter.onLayout(layoutSpec.oldAttributes,
-                                    layoutSpec.newAttributes, cancellation,
-                                    new MyLayoutResultCallback(layoutSpec.callback,
-                                            layoutSpec.sequence), layoutSpec.metadata);
-                        }
-                    } break;
-
-                    case MSG_WRITE: {
-                        final PrintDocumentAdapter adapter;
-                        final CancellationSignal cancellation;
-                        final WriteSpec writeSpec;
-
-                        synchronized (mLock) {
-                            adapter = mDocumentAdapter;
-                            writeSpec = mLastWriteSpec;
-                            mLastWriteSpec = null;
-                            cancellation = new CancellationSignal();
-                            mLayoutOrWriteCancellation = cancellation;
-                        }
-
-                        if (writeSpec != null && adapter != null) {
-                            if (DEBUG) {
-                                Log.i(LOG_TAG, "Performing write");
-                            }
-                            adapter.onWrite(writeSpec.pages, writeSpec.fd,
-                                    cancellation, new MyWriteResultCallback(writeSpec.callback,
-                                            writeSpec.fd, writeSpec.sequence));
-                        }
-                    } break;
-
-                    case MSG_FINISH: {
+                    case MSG_ON_START: {
                         if (DEBUG) {
-                            Log.i(LOG_TAG, "Performing finish");
+                            Log.i(LOG_TAG, "onStart()");
                         }
-                        final PrintDocumentAdapter adapter;
-                        final Activity activity;
+
+                        ((PrintDocumentAdapter) message.obj).onStart();
+                    } break;
+
+                    case MSG_ON_LAYOUT: {
+                        SomeArgs args = (SomeArgs) message.obj;
+                        PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1;
+                        PrintAttributes oldAttributes = (PrintAttributes) args.arg2;
+                        PrintAttributes newAttributes = (PrintAttributes) args.arg3;
+                        CancellationSignal cancellation = (CancellationSignal) args.arg4;
+                        LayoutResultCallback callback = (LayoutResultCallback) args.arg5;
+                        Bundle metadata = (Bundle) args.arg6;
+                        args.recycle();
+
+                        if (DEBUG) {
+                            StringBuilder builder = new StringBuilder();
+                            builder.append("PrintDocumentAdapter#onLayout() {\n");
+                            builder.append("\n  oldAttributes:").append(oldAttributes);
+                            builder.append("\n  newAttributes:").append(newAttributes);
+                            builder.append("\n  preview:").append(metadata.getBoolean(
+                                    PrintDocumentAdapter.EXTRA_PRINT_PREVIEW));
+                            builder.append("\n}");
+                            Log.i(LOG_TAG, builder.toString());
+                        }
+
+                        adapter.onLayout(oldAttributes, newAttributes, cancellation,
+                                callback, metadata);
+                    } break;
+
+                    case MSG_ON_WRITE: {
+                        SomeArgs args = (SomeArgs) message.obj;
+                        PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1;
+                        PageRange[] pages = (PageRange[]) args.arg2;
+                        ParcelFileDescriptor fd = (ParcelFileDescriptor) args.arg3;
+                        CancellationSignal cancellation = (CancellationSignal) args.arg4;
+                        WriteResultCallback callback = (WriteResultCallback) args.arg5;
+                        args.recycle();
+
+                        if (DEBUG) {
+                            StringBuilder builder = new StringBuilder();
+                            builder.append("PrintDocumentAdapter#onWrite() {\n");
+                            builder.append("\n  pages:").append(Arrays.toString(pages));
+                            builder.append("\n}");
+                            Log.i(LOG_TAG, builder.toString());
+                        }
+
+                        adapter.onWrite(pages, fd, cancellation, callback);
+                    } break;
+
+                    case MSG_ON_FINISH: {
+                        if (DEBUG) {
+                            Log.i(LOG_TAG, "onFinish()");
+                        }
+
+                        ((PrintDocumentAdapter) message.obj).onFinish();
+
+                        // Done printing, so destroy this instance as it
+                        // should not be used anymore.
                         synchronized (mLock) {
-                            adapter = mDocumentAdapter;
-                            activity = mActivity;
-                            clearLocked();
-                        }
-                        if (adapter != null) {
-                            adapter.onFinish();
-                        }
-                        if (activity != null) {
-                            activity.getApplication().unregisterActivityLifecycleCallbacks(
-                                    PrintDocumentAdapterDelegate.this);
+                            destroyLocked();
                         }
                     } break;
 
@@ -898,7 +802,12 @@
             }
         }
 
-        private final class MyLayoutResultCallback extends LayoutResultCallback {
+        private interface DestroyableCallback {
+            public void destroy();
+        }
+
+        private final class MyLayoutResultCallback extends LayoutResultCallback
+                implements DestroyableCallback {
             private ILayoutResultCallback mCallback;
             private final int mSequence;
 
@@ -910,25 +819,31 @@
 
             @Override
             public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
-                if (info == null) {
-                    throw new NullPointerException("document info cannot be null");
-                }
                 final ILayoutResultCallback callback;
                 synchronized (mLock) {
-                    if (mDestroyed) {
-                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
-                                + "finish the printing activity before print completion?");
-                        return;
-                    }
                     callback = mCallback;
-                    clearLocked();
                 }
-                if (callback != null) {
+
+                // If the callback is null we are destroyed.
+                if (callback == null) {
+                    Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                            + "finish the printing activity before print completion "
+                            + "or did you invoke a callback after finish?");
+                    return;
+                }
+
+                try {
+                    if (info == null) {
+                        throw new NullPointerException("document info cannot be null");
+                    }
+
                     try {
                         callback.onLayoutFinished(info, changed, mSequence);
                     } catch (RemoteException re) {
                         Log.e(LOG_TAG, "Error calling onLayoutFinished", re);
                     }
+                } finally {
+                    destroy();
                 }
             }
 
@@ -936,46 +851,64 @@
             public void onLayoutFailed(CharSequence error) {
                 final ILayoutResultCallback callback;
                 synchronized (mLock) {
-                    if (mDestroyed) {
-                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
-                                + "finish the printing activity before print completion?");
-                        return;
-                    }
                     callback = mCallback;
-                    clearLocked();
                 }
-                if (callback != null) {
-                    try {
-                        callback.onLayoutFailed(error, mSequence);
-                    } catch (RemoteException re) {
-                        Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
-                    }
+
+                // If the callback is null we are destroyed.
+                if (callback == null) {
+                    Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                            + "finish the printing activity before print completion "
+                            + "or did you invoke a callback after finish?");
+                    return;
+                }
+
+                try {
+                    callback.onLayoutFailed(error, mSequence);
+                } catch (RemoteException re) {
+                    Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
+                } finally {
+                    destroy();
                 }
             }
 
             @Override
             public void onLayoutCancelled() {
+                final ILayoutResultCallback callback;
                 synchronized (mLock) {
-                    if (mDestroyed) {
-                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
-                                + "finish the printing activity before print completion?");
-                        return;
-                    }
-                    clearLocked();
+                    callback = mCallback;
+                }
+
+                // If the callback is null we are destroyed.
+                if (callback == null) {
+                    Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                            + "finish the printing activity before print completion "
+                            + "or did you invoke a callback after finish?");
+                    return;
+                }
+
+                try {
+                    callback.onLayoutCanceled(mSequence);
+                } catch (RemoteException re) {
+                    Log.e(LOG_TAG, "Error calling onLayoutFailed", re);
+                } finally {
+                    destroy();
                 }
             }
 
-            private void clearLocked() {
-                mLayoutOrWriteCancellation = null;
-                mCallback = null;
-                doPendingWorkLocked();
+            @Override
+            public void destroy() {
+                synchronized (mLock) {
+                    mCallback = null;
+                    mPendingCallback = null;
+                }
             }
         }
 
-        private final class MyWriteResultCallback extends WriteResultCallback {
+        private final class MyWriteResultCallback extends WriteResultCallback
+                implements DestroyableCallback {
             private ParcelFileDescriptor mFd;
-            private int mSequence;
             private IWriteResultCallback mCallback;
+            private final int mSequence;
 
             public MyWriteResultCallback(IWriteResultCallback callback,
                     ParcelFileDescriptor fd, int sequence) {
@@ -988,26 +921,32 @@
             public void onWriteFinished(PageRange[] pages) {
                 final IWriteResultCallback callback;
                 synchronized (mLock) {
-                    if (mDestroyed) {
-                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
-                                + "finish the printing activity before print completion?");
-                        return;
-                    }
                     callback = mCallback;
-                    clearLocked();
                 }
-                if (pages == null) {
-                    throw new IllegalArgumentException("pages cannot be null");
+
+                // If the callback is null we are destroyed.
+                if (callback == null) {
+                    Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                            + "finish the printing activity before print completion "
+                            + "or did you invoke a callback after finish?");
+                    return;
                 }
-                if (pages.length == 0) {
-                    throw new IllegalArgumentException("pages cannot be empty");
-                }
-                if (callback != null) {
+
+                try {
+                    if (pages == null) {
+                        throw new IllegalArgumentException("pages cannot be null");
+                    }
+                    if (pages.length == 0) {
+                        throw new IllegalArgumentException("pages cannot be empty");
+                    }
+
                     try {
                         callback.onWriteFinished(pages, mSequence);
                     } catch (RemoteException re) {
                         Log.e(LOG_TAG, "Error calling onWriteFinished", re);
                     }
+                } finally {
+                    destroy();
                 }
             }
 
@@ -1015,41 +954,58 @@
             public void onWriteFailed(CharSequence error) {
                 final IWriteResultCallback callback;
                 synchronized (mLock) {
-                    if (mDestroyed) {
-                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
-                                + "finish the printing activity before print completion?");
-                        return;
-                    }
                     callback = mCallback;
-                    clearLocked();
                 }
-                if (callback != null) {
-                    try {
-                        callback.onWriteFailed(error, mSequence);
-                    } catch (RemoteException re) {
-                        Log.e(LOG_TAG, "Error calling onWriteFailed", re);
-                    }
+
+                // If the callback is null we are destroyed.
+                if (callback == null) {
+                    Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                            + "finish the printing activity before print completion "
+                            + "or did you invoke a callback after finish?");
+                    return;
+                }
+
+                try {
+                    callback.onWriteFailed(error, mSequence);
+                } catch (RemoteException re) {
+                    Log.e(LOG_TAG, "Error calling onWriteFailed", re);
+                } finally {
+                    destroy();
                 }
             }
 
             @Override
             public void onWriteCancelled() {
+                final IWriteResultCallback callback;
                 synchronized (mLock) {
-                    if (mDestroyed) {
-                        Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
-                                + "finish the printing activity before print completion?");
-                        return;
-                    }
-                    clearLocked();
+                    callback = mCallback;
+                }
+
+                // If the callback is null we are destroyed.
+                if (callback == null) {
+                    Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you "
+                            + "finish the printing activity before print completion "
+                            + "or did you invoke a callback after finish?");
+                    return;
+                }
+
+                try {
+                    callback.onWriteCanceled(mSequence);
+                } catch (RemoteException re) {
+                    Log.e(LOG_TAG, "Error calling onWriteCanceled", re);
+                } finally {
+                    destroy();
                 }
             }
 
-            private void clearLocked() {
-                mLayoutOrWriteCancellation = null;
-                IoUtils.closeQuietly(mFd);
-                mCallback = null;
-                mFd = null;
-                doPendingWorkLocked();
+            @Override
+            public void destroy() {
+                synchronized (mLock) {
+                    IoUtils.closeQuietly(mFd);
+                    mCallback = null;
+                    mFd = null;
+                    mPendingCallback = null;
+                }
             }
         }
     }
diff --git a/core/java/android/print/PrinterDiscoverySession.java b/core/java/android/print/PrinterDiscoverySession.java
index d32b71b..abb441b 100644
--- a/core/java/android/print/PrinterDiscoverySession.java
+++ b/core/java/android/print/PrinterDiscoverySession.java
@@ -72,9 +72,9 @@
         }
     }
 
-    public final void startPrinterDisovery(List<PrinterId> priorityList) {
+    public final void startPrinterDiscovery(List<PrinterId> priorityList) {
         if (isDestroyed()) {
-            Log.w(LOG_TAG, "Ignoring start printers dsicovery - session destroyed");
+            Log.w(LOG_TAG, "Ignoring start printers discovery - session destroyed");
             return;
         }
         if (!mIsPrinterDiscoveryStarted) {
@@ -122,7 +122,7 @@
         try {
             mPrintManager.stopPrinterStateTracking(printerId, mUserId);
         } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error stoping printer state tracking", re);
+            Log.e(LOG_TAG, "Error stopping printer state tracking", re);
         }
     }
 
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index eb0ac2e..1557ab0 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -201,9 +201,9 @@
      * should build another one using the {@link PrintJobInfo.Builder} class. You
      * can specify any standard properties and add advanced, printer specific,
      * ones via {@link PrintJobInfo.Builder#putAdvancedOption(String, String)
-     * PrintJobInfo.Builder#putAdvancedOption(String, String)} and {@link
+     * PrintJobInfo.Builder.putAdvancedOption(String, String)} and {@link
      * PrintJobInfo.Builder#putAdvancedOption(String, int)
-     * PrintJobInfo.Builder#putAdvancedOption(String, int)}. The advanced options
+     * PrintJobInfo.Builder.putAdvancedOption(String, int)}. The advanced options
      * are not interpreted by the system, they will not be visible to applications,
      * and can only be accessed by your print service via {@link
      * PrintJob#getAdvancedStringOption(String) PrintJob.getAdvancedStringOption(String)}
@@ -212,14 +212,26 @@
      * <p>
      * If the advanced print options activity offers changes to the standard print
      * options, you can get the current {@link android.print.PrinterInfo} using the
-     * "android.intent.extra.print.EXTRA_PRINTER_INFO" extra which will allow you to
-     * present the user with UI options supported by the current printer. For example,
-     * if the current printer does not support a give media size, you should not
-     * offer it in the advanced print options dialog.
+     * {@link #EXTRA_PRINTER_INFO} extra which will allow you to present the user
+     * with UI options supported by the current printer. For example, if the current
+     * printer does not support a given media size, you should not offer it in the
+     * advanced print options UI.
      * </p>
+     *
+     * @see #EXTRA_PRINTER_INFO
      */
     public static final String EXTRA_PRINT_JOB_INFO = "android.intent.extra.print.PRINT_JOB_INFO";
 
+    /**
+     * If you declared an optional activity with advanced print options via the
+     * {@link R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
+     * attribute, this extra is used to pass in the currently selected printer's
+     * {@link android.print.PrinterInfo} to your activity allowing you to inspect it.
+     *
+     * @see #EXTRA_PRINT_JOB_INFO
+     */
+    public static final String EXTRA_PRINTER_INFO = "android.intent.extra.print.PRINTER_INFO";
+
     private Handler mHandler;
 
     private IPrintServiceClient mClient;
diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java
index a34d9c3..3853003 100644
--- a/core/java/android/provider/Browser.java
+++ b/core/java/android/provider/Browser.java
@@ -25,6 +25,7 @@
 import android.database.DatabaseUtils;
 import android.graphics.BitmapFactory;
 import android.net.Uri;
+import android.os.Build;
 import android.provider.BrowserContract.Bookmarks;
 import android.provider.BrowserContract.Combined;
 import android.provider.BrowserContract.History;
@@ -155,8 +156,8 @@
      *  @param title    Title for the bookmark. Can be null or empty string.
      *  @param url      Url for the bookmark. Can be null or empty string.
      */
-    public static final void saveBookmark(Context c, 
-                                          String title, 
+    public static final void saveBookmark(Context c,
+                                          String title,
                                           String url) {
         Intent i = new Intent(Intent.ACTION_INSERT, Browser.BOOKMARKS_URI);
         i.putExtra("title", title);
@@ -233,10 +234,10 @@
      *
      *  @param cr   The ContentResolver used to access the database.
      */
-    public static final Cursor getAllBookmarks(ContentResolver cr) throws 
+    public static final Cursor getAllBookmarks(ContentResolver cr) throws
             IllegalStateException {
         return cr.query(Bookmarks.CONTENT_URI,
-                new String[] { Bookmarks.URL }, 
+                new String[] { Bookmarks.URL },
                 Bookmarks.IS_FOLDER + " = 0", null, null);
     }
 
@@ -397,19 +398,17 @@
         // TODO make a single request to the provider to do this in a single transaction
         Cursor cursor = null;
         try {
-            
+
             // Select non-bookmark history, ordered by date
             cursor = cr.query(History.CONTENT_URI,
                     new String[] { History._ID, History.URL, History.DATE_LAST_VISITED },
                     null, null, History.DATE_LAST_VISITED + " ASC");
 
             if (cursor.moveToFirst() && cursor.getCount() >= MAX_HISTORY_COUNT) {
-                final WebIconDatabase iconDb = WebIconDatabase.getInstance();
                 /* eliminate oldest history items */
                 for (int i = 0; i < TRUNCATE_N_OLDEST; i++) {
                     cr.delete(ContentUris.withAppendedId(History.CONTENT_URI, cursor.getLong(0)),
-                            null, null);
-                    iconDb.releaseIconForPageUrl(cursor.getString(1));
+                        null, null);
                     if (!cursor.moveToNext()) break;
                 }
             }
@@ -469,13 +468,6 @@
             cursor = cr.query(History.CONTENT_URI, new String[] { History.URL }, whereClause,
                     null, null);
             if (cursor.moveToFirst()) {
-                final WebIconDatabase iconDb = WebIconDatabase.getInstance();
-                do {
-                    // Delete favicons
-                    // TODO don't release if the URL is bookmarked
-                    iconDb.releaseIconForPageUrl(cursor.getString(0));
-                } while (cursor.moveToNext());
-
                 cr.delete(History.CONTENT_URI, whereClause, null);
             }
         } catch (IllegalStateException e) {
@@ -520,7 +512,7 @@
      * @param cr    The ContentResolver used to access the database.
      * @param url   url to remove.
      */
-    public static final void deleteFromHistory(ContentResolver cr, 
+    public static final void deleteFromHistory(ContentResolver cr,
                                                String url) {
         cr.delete(History.CONTENT_URI, History.URL + "=?", new String[] { url });
     }
@@ -554,7 +546,7 @@
             Log.e(LOGTAG, "clearSearches", e);
         }
     }
-    
+
     /**
      *  Request all icons from the database.  This call must either be called
      *  in the main thread or have had Looper.prepare() invoked in the calling
@@ -563,12 +555,12 @@
      *  @param  cr The ContentResolver used to access the database.
      *  @param  where Clause to be used to limit the query from the database.
      *          Must be an allowable string to be passed into a database query.
-     *  @param  listener IconListener that gets the icons once they are 
+     *  @param  listener IconListener that gets the icons once they are
      *          retrieved.
      */
     public static final void requestAllIcons(ContentResolver cr, String where,
             WebIconDatabase.IconListener listener) {
-        WebIconDatabase.getInstance().bulkRequestIconForPageUrl(cr, where, listener);
+        // Do nothing: this is no longer used.
     }
 
     /**
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 8c7e879..ba66e65 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -1156,8 +1156,6 @@
      * address book index, which is usually the first letter of the sort key.
      * When this parameter is supplied, the row counts are returned in the
      * cursor extras bundle.
-     *
-     * @hide
      */
     public final static class ContactCounts {
 
@@ -1167,7 +1165,24 @@
          * first letter of the sort key. This parameter does not affect the main
          * content of the cursor.
          *
-         * @hide
+         * <p>
+         * <pre>
+         * Example:
+         * Uri uri = Contacts.CONTENT_URI.buildUpon()
+         *          .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true")
+         *          .build();
+         * Cursor cursor = getContentResolver().query(uri,
+         *          new String[] {Contacts.DISPLAY_NAME},
+         *          null, null, null);
+         * Bundle bundle = cursor.getExtras();
+         * if (bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES) &&
+         *         bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS)) {
+         *     String sections[] =
+         *             bundle.getStringArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
+         *     int counts[] = bundle.getIntArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
+         * }
+         * </pre>
+         * </p>
          */
         public static final String ADDRESS_BOOK_INDEX_EXTRAS = "address_book_index_extras";
 
@@ -1175,8 +1190,6 @@
          * The array of address book index titles, which are returned in the
          * same order as the data in the cursor.
          * <p>TYPE: String[]</p>
-         *
-         * @hide
          */
         public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "address_book_index_titles";
 
@@ -1184,8 +1197,6 @@
          * The array of group counts for the corresponding group.  Contains the same number
          * of elements as the EXTRA_ADDRESS_BOOK_INDEX_TITLES array.
          * <p>TYPE: int[]</p>
-         *
-         * @hide
          */
         public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "address_book_index_counts";
     }
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index cfab1b3..0fe764f 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -1886,6 +1886,9 @@
              * The MIME type for entries in this table.
              */
             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/radio";
+
+            // Not instantiable.
+            private Radio() { }
         }
     }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 55c66ba..3fe0fb8 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4470,6 +4470,12 @@
                 INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF;
 
         /**
+         * Whether the device should wake when the wake gesture sensor detects motion.
+         * @hide
+         */
+        public static final String WAKE_GESTURE_ENABLED = "wake_gesture_enabled";
+
+        /**
          * The current night mode that has been selected by the user.  Owned
          * and controlled by UiModeManagerService.  Constants are as per
          * UiModeManager.
@@ -4570,6 +4576,16 @@
         public static final String DISPLAY_INTERCEPTED_NOTIFICATIONS = "display_intercepted_notifications";
 
         /**
+         * If enabled, apps should try to skip any introductory hints on first launch. This might
+         * apply to users that are already familiar with the environment or temporary users, like
+         * guests.
+         * <p>
+         * Type : int (0 to show hints, 1 to skip showing hints)
+         * @hide
+         */
+        public static final String SKIP_FIRST_USE_HINTS = "skip_first_use_hints";
+
+        /**
          * This are the settings to be backed up.
          *
          * NOTE: Settings are backed up and restored in the order they appear
@@ -6210,6 +6226,14 @@
         public static final String DEVICE_NAME = "device_name";
 
         /**
+         * Whether it should be possible to create a guest user on the device.
+         * <p>
+         * Type: int (0 for disabled, 1 for enabled)
+         * @hide
+         */
+        public static final String GUEST_USER_ENABLED = "guest_user_enabled";
+
+        /**
          * Settings to backup. This is here so that it's in the same place as the settings
          * keys and easy to update.
          *
diff --git a/core/java/android/service/fingerprint/FingerprintManager.java b/core/java/android/service/fingerprint/FingerprintManager.java
index dd2030b..2fcec52 100644
--- a/core/java/android/service/fingerprint/FingerprintManager.java
+++ b/core/java/android/service/fingerprint/FingerprintManager.java
@@ -31,7 +31,6 @@
 
 /**
  * A class that coordinates access to the fingerprint hardware.
- * @hide
  */
 
 public class FingerprintManager {
diff --git a/core/java/android/service/fingerprint/FingerprintManagerReceiver.java b/core/java/android/service/fingerprint/FingerprintManagerReceiver.java
index 5960791..34f1655 100644
--- a/core/java/android/service/fingerprint/FingerprintManagerReceiver.java
+++ b/core/java/android/service/fingerprint/FingerprintManagerReceiver.java
@@ -13,7 +13,6 @@
  * 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.
- * @hide
  */
 
 public class FingerprintManagerReceiver {
diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java
index ed835e4..a6cddae 100644
--- a/core/java/android/service/trust/TrustAgentService.java
+++ b/core/java/android/service/trust/TrustAgentService.java
@@ -17,7 +17,6 @@
 package android.service.trust;
 
 import android.Manifest;
-import android.annotation.SystemApi;
 import android.annotation.SdkConstant;
 import android.app.Service;
 import android.content.ComponentName;
@@ -57,10 +56,7 @@
  * <pre>
  * &lt;trust-agent xmlns:android="http://schemas.android.com/apk/res/android"
  *          android:settingsActivity=".TrustAgentSettings" /></pre>
- *
- * @hide
  */
-@SystemApi
 public class TrustAgentService extends Service {
     private final String TAG = TrustAgentService.class.getSimpleName() +
             "[" + getClass().getSimpleName() + "]";
diff --git a/core/java/android/service/voice/DspInfo.java b/core/java/android/service/voice/DspInfo.java
new file mode 100644
index 0000000..0862309
--- /dev/null
+++ b/core/java/android/service/voice/DspInfo.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.voice;
+
+import java.util.UUID;
+
+/**
+ * Properties of the DSP hardware on the device.
+ * @hide
+ */
+public class DspInfo {
+    /**
+     * Unique voice engine Id (changes with each version).
+     */
+    public final UUID voiceEngineId;
+
+    /**
+     * Human readable voice detection engine implementor.
+     */
+    public final String voiceEngineImplementor;
+    /**
+     * Human readable voice detection engine description.
+     */
+    public final String voiceEngineDescription;
+    /**
+     * Human readable voice detection engine version
+     */
+    public final int voiceEngineVersion;
+    /**
+     * Rated power consumption when detection is active.
+     */
+    public final int powerConsumptionMw;
+
+    public DspInfo(UUID voiceEngineId, String voiceEngineImplementor,
+            String voiceEngineDescription, int version, int powerConsumptionMw) {
+        this.voiceEngineId = voiceEngineId;
+        this.voiceEngineImplementor = voiceEngineImplementor;
+        this.voiceEngineDescription = voiceEngineDescription;
+        this.voiceEngineVersion = version;
+        this.powerConsumptionMw = powerConsumptionMw;
+    }
+}
diff --git a/core/java/android/service/voice/KeyphraseEnrollmentInfo.java b/core/java/android/service/voice/KeyphraseEnrollmentInfo.java
new file mode 100644
index 0000000..ebe41ce
--- /dev/null
+++ b/core/java/android/service/voice/KeyphraseEnrollmentInfo.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.voice;
+
+import android.Manifest;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.Slog;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.List;
+
+/** @hide */
+public class KeyphraseEnrollmentInfo {
+    private static final String TAG = "KeyphraseEnrollmentInfo";
+    /**
+     * Name under which a Hotword enrollment component publishes information about itself.
+     * This meta-data should reference an XML resource containing a
+     * <code>&lt;{@link
+     * android.R.styleable#VoiceEnrollmentApplication
+     * voice-enrollment-application}&gt;</code> tag.
+     */
+    private static final String VOICE_KEYPHRASE_META_DATA = "android.voice_enrollment";
+    /**
+     * Activity Action: Show activity for managing the keyphrases for hotword detection.
+     * This needs to be defined by an activity that supports enrolling users for hotword/keyphrase
+     * detection.
+     */
+    public static final String ACTION_MANAGE_VOICE_KEYPHRASES =
+            "com.android.intent.action.MANAGE_VOICE_KEYPHRASES";
+    /**
+     * Intent extra: The intent extra for un-enrolling a user for a particular keyphrase.
+     */
+    public static final String EXTRA_VOICE_KEYPHRASE_UNENROLL =
+            "com.android.intent.extra.VOICE_KEYPHRASE_UNENROLL";
+    /**
+     * Intent extra: The hint text to be shown on the voice keyphrase management UI.
+     */
+    public static final String EXTRA_VOICE_KEYPHRASE_HINT_TEXT =
+            "com.android.intent.extra.VOICE_KEYPHRASE_HINT_TEXT";
+    /**
+     * Intent extra: The voice locale to use while managing the keyphrase.
+     */
+    public static final String EXTRA_VOICE_KEYPHRASE_LOCALE =
+            "com.android.intent.extra.VOICE_KEYPHRASE_LOCALE";
+
+    private KeyphraseInfo[] mKeyphrases;
+    private String mEnrollmentPackage;
+    private String mParseError;
+
+    public KeyphraseEnrollmentInfo(PackageManager pm) {
+        // Find the apps that supports enrollment for hotword keyhphrases,
+        // Pick a privileged app and obtain the information about the supported keyphrases
+        // from its metadata.
+        List<ResolveInfo> ris = pm.queryIntentActivities(
+                new Intent(ACTION_MANAGE_VOICE_KEYPHRASES), PackageManager.MATCH_DEFAULT_ONLY);
+        if (ris == null || ris.isEmpty()) {
+            // No application capable of enrolling for voice keyphrases is present.
+            mParseError = "No enrollment application found";
+            return;
+        }
+
+        boolean found = false;
+        ApplicationInfo ai = null;
+        for (ResolveInfo ri : ris) {
+            try {
+                ai = pm.getApplicationInfo(
+                        ri.activityInfo.packageName, PackageManager.GET_META_DATA);
+                if ((ai.flags & ApplicationInfo.FLAG_PRIVILEGED) == 0) {
+                    // The application isn't privileged (/system/priv-app).
+                    // The enrollment application needs to be a privileged system app.
+                    Slog.w(TAG, ai.packageName + "is not a privileged system app");
+                    continue;
+                }
+                if (!Manifest.permission.MANAGE_VOICE_KEYPHRASES.equals(ai.permission)) {
+                    // The application trying to manage keyphrases doesn't
+                    // require the MANAGE_VOICE_KEYPHRASES permission.
+                    Slog.w(TAG, ai.packageName + " does not require MANAGE_VOICE_KEYPHRASES");
+                    continue;
+                }
+                mEnrollmentPackage = ai.packageName;
+                found = true;
+                break;
+            } catch (PackageManager.NameNotFoundException e) {
+                Slog.w(TAG, "error parsing voice enrollment meta-data", e);
+            }
+        }
+
+        if (!found) {
+            mKeyphrases = null;
+            mParseError = "No suitable enrollment application found";
+            return;
+        }
+
+        XmlResourceParser parser = null;
+        try {
+            parser = ai.loadXmlMetaData(pm, VOICE_KEYPHRASE_META_DATA);
+            if (parser == null) {
+                mParseError = "No " + VOICE_KEYPHRASE_META_DATA + " meta-data for "
+                        + ai.packageName;
+                return;
+            }
+
+            Resources res = pm.getResourcesForApplication(ai);
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+
+            int type;
+            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                    && type != XmlPullParser.START_TAG) {
+            }
+
+            String nodeName = parser.getName();
+            if (!"voice-enrollment-application".equals(nodeName)) {
+                mParseError = "Meta-data does not start with voice-enrollment-application tag";
+                return;
+            }
+
+            TypedArray array = res.obtainAttributes(attrs,
+                    com.android.internal.R.styleable.VoiceEnrollmentApplication);
+            int searchKeyphraseId = array.getInt(
+                    com.android.internal.R.styleable.VoiceEnrollmentApplication_searchKeyphraseId,
+                    -1);
+            if (searchKeyphraseId != -1) {
+                String searchKeyphrase = array.getString(com.android.internal.R.styleable
+                        .VoiceEnrollmentApplication_searchKeyphrase);
+                String searchKeyphraseSupportedLocales =
+                        array.getString(com.android.internal.R.styleable
+                                .VoiceEnrollmentApplication_searchKeyphraseSupportedLocales);
+                String[] supportedLocales = new String[0];
+                // Get all the supported locales from the comma-delimted string.
+                if (searchKeyphraseSupportedLocales != null
+                        && !searchKeyphraseSupportedLocales.isEmpty()) {
+                    supportedLocales = searchKeyphraseSupportedLocales.split(",");
+                }
+                mKeyphrases = new KeyphraseInfo[1];
+                mKeyphrases[0] = new KeyphraseInfo(
+                        searchKeyphraseId, searchKeyphrase, supportedLocales);
+            } else {
+                mParseError = "searchKeyphraseId not specified in meta-data";
+                return;
+            }
+        } catch (XmlPullParserException e) {
+            mParseError = "Error parsing keyphrase enrollment meta-data: " + e;
+            Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e);
+            return;
+        } catch (IOException e) {
+            mParseError = "Error parsing keyphrase enrollment meta-data: " + e;
+            Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e);
+            return;
+        } catch (PackageManager.NameNotFoundException e) {
+            mParseError = "Error parsing keyphrase enrollment meta-data: " + e;
+            Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e);
+            return;
+        } finally {
+            if (parser != null) parser.close();
+        }
+    }
+
+    public String getParseError() {
+        return mParseError;
+    }
+
+    /**
+     * @return An array of available keyphrases that can be enrolled on the system.
+     *         It may be null if no keyphrases can be enrolled.
+     */
+    public KeyphraseInfo[] getKeyphrases() {
+        return mKeyphrases;
+    }
+
+    /**
+     * Returns an intent to launch an activity that manages the given keyphrase
+     * for the locale.
+     *
+     * @param enroll Indicates if the intent should enroll the user or un-enroll them.
+     * @param keyphrase The keyphrase that the user needs to be enrolled to.
+     * @param locale The locale for which the enrollment needs to be performed.
+     * @return An {@link Intent} to manage the keyphrase. This can be null if managing the
+     *         given keyphrase/locale combination isn't possible.
+     */
+    public Intent getManageKeyphraseIntent(boolean enroll, String keyphrase, String locale) {
+        if (mEnrollmentPackage == null || mEnrollmentPackage.isEmpty()) {
+            Slog.w(TAG, "No enrollment application exists");
+            return null;
+        }
+
+        if (isKeyphraseEnrollmentSupported(keyphrase, locale)) {
+            Intent intent = new Intent(ACTION_MANAGE_VOICE_KEYPHRASES)
+                    .setPackage(mEnrollmentPackage)
+                    .putExtra(EXTRA_VOICE_KEYPHRASE_HINT_TEXT, keyphrase)
+                    .putExtra(EXTRA_VOICE_KEYPHRASE_LOCALE, locale);
+            if (!enroll) intent.putExtra(EXTRA_VOICE_KEYPHRASE_UNENROLL, true);
+            return intent;
+        }
+        return null;
+    }
+
+    /**
+     * Indicates if enrollment is supported for the given keyphrase & locale.
+     *
+     * @param keyphrase The keyphrase that the user needs to be enrolled to.
+     * @param locale The locale for which the enrollment needs to be performed.
+     * @return true, if an enrollment client supports the given keyphrase and the given locale.
+     */
+    public boolean isKeyphraseEnrollmentSupported(String keyphrase, String locale) {
+        if (mKeyphrases == null || mKeyphrases.length == 0) {
+            Slog.w(TAG, "Enrollment application doesn't support keyphrases");
+            return false;
+        }
+        for (KeyphraseInfo keyphraseInfo : mKeyphrases) {
+            // Check if the given keyphrase is supported in the locale provided by
+            // the enrollment application.
+            String supportedKeyphrase = keyphraseInfo.keyphrase;
+            if (supportedKeyphrase.equalsIgnoreCase(keyphrase)
+                    && keyphraseInfo.supportedLocales.contains(locale)) {
+                return true;
+            }
+        }
+        Slog.w(TAG, "Enrollment application doesn't support the given keyphrase");
+        return false;
+    }
+}
diff --git a/core/java/android/service/voice/KeyphraseInfo.java b/core/java/android/service/voice/KeyphraseInfo.java
new file mode 100644
index 0000000..d266e1a
--- /dev/null
+++ b/core/java/android/service/voice/KeyphraseInfo.java
@@ -0,0 +1,27 @@
+package android.service.voice;
+
+import android.util.ArraySet;
+
+/**
+ * A Voice Keyphrase.
+ * @hide
+ */
+public class KeyphraseInfo {
+    public final int id;
+    public final String keyphrase;
+    public final ArraySet<String> supportedLocales;
+
+    public KeyphraseInfo(int id, String keyphrase, String[] supportedLocales) {
+        this.id = id;
+        this.keyphrase = keyphrase;
+        this.supportedLocales = new ArraySet<String>(supportedLocales.length);
+        for (String locale : supportedLocales) {
+            this.supportedLocales.add(locale);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "id=" + id + ", keyphrase=" + keyphrase + ", supported-locales=" + supportedLocales;
+    }
+}
diff --git a/core/java/android/service/voice/SoundTriggerManager.java b/core/java/android/service/voice/SoundTriggerManager.java
new file mode 100644
index 0000000..2d049b9
--- /dev/null
+++ b/core/java/android/service/voice/SoundTriggerManager.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.voice;
+
+import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
+
+import java.util.ArrayList;
+
+/**
+ * Manager for {@link SoundTrigger} APIs.
+ * Currently this just acts as an abstraction over all SoundTrigger API calls.
+ * @hide
+ */
+public class SoundTriggerManager {
+    /** The {@link DspInfo} for the system, or null if none exists. */
+    public DspInfo dspInfo;
+
+    public SoundTriggerManager() {
+        ArrayList <ModuleProperties> modules = new ArrayList<>();
+        int status = SoundTrigger.listModules(modules);
+        if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
+            // TODO(sansid, elaurent): Figure out how to handle errors in listing the modules here.
+            dspInfo = null;
+        } else {
+            // TODO(sansid, elaurent): Figure out how to determine which module corresponds to the
+            // DSP hardware.
+            ModuleProperties properties = modules.get(0);
+            dspInfo = new DspInfo(properties.uuid, properties.implementor, properties.description,
+                    properties.version, properties.powerConsumptionMw);
+        }
+    }
+
+    /**
+     * @return True, if the keyphrase is supported on DSP for the given locale.
+     */
+    public boolean isKeyphraseSupported(String keyphrase, String locale) {
+        // TODO(sansid): We also need to look into a SoundTrigger API that let's us
+        // query this. For now just return supported if there's a DSP available.
+        return dspInfo != null;
+    }
+
+    /**
+     * @return True, if the keyphrase is has been enrolled for the given locale.
+     */
+    public boolean isKeyphraseEnrolled(String keyphrase, String locale) {
+        // TODO(sansid, elaurent): Query SoundTrigger to list currently loaded sound models.
+        // They have been enrolled.
+        return false;
+    }
+
+    /**
+     * @return True, if a recognition for the keyphrase is active for the given locale.
+     */
+    public boolean isKeyphraseActive(String keyphrase, String locale) {
+        // TODO(sansid, elaurent): Check if the recognition for the keyphrase is currently active.
+        return false;
+    }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index e15489b..e0329f8 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -17,7 +17,6 @@
 package android.service.voice;
 
 import android.annotation.SdkConstant;
-import android.app.Instrumentation;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
@@ -25,8 +24,11 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractionManagerService;
 
+
 /**
  * Top-level service of the current global voice interactor, which is providing
  * support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc.
@@ -51,6 +53,16 @@
     public static final String SERVICE_INTERFACE =
             "android.service.voice.VoiceInteractionService";
 
+    // TODO(sansid): Unhide these.
+    /** @hide */
+    public static final int KEYPHRASE_UNAVAILABLE = 0;
+    /** @hide */
+    public static final int KEYPHRASE_UNENROLLED = 1;
+    /** @hide */
+    public static final int KEYPHRASE_ENROLLED = 2;
+    /** @hide */
+    public static final int KEYPHRASE_ACTIVE = 3;
+
     /**
      * Name under which a VoiceInteractionService component publishes information about itself.
      * This meta-data should reference an XML resource containing a
@@ -64,6 +76,9 @@
 
     IVoiceInteractionManagerService mSystemService;
 
+    private SoundTriggerManager mSoundTriggerManager;
+    private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
+
     public void startSession(Bundle args) {
         try {
             mSystemService.startSession(mInterface, args);
@@ -76,6 +91,8 @@
         super.onCreate();
         mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
                 ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+        mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager());
+        mSoundTriggerManager = new SoundTriggerManager();
     }
 
     @Override
@@ -85,4 +102,44 @@
         }
         return null;
     }
+
+    /**
+     * Gets the state of always-on hotword detection for the given keyphrase and locale
+     * on this system.
+     * Availability implies that the hardware on this system is capable of listening for
+     * the given keyphrase or not.
+     * The return code is one of {@link #KEYPHRASE_UNAVAILABLE}, {@link #KEYPHRASE_UNENROLLED}
+     * {@link #KEYPHRASE_ENROLLED} or {@link #KEYPHRASE_ACTIVE}.
+     *
+     * @param keyphrase The keyphrase whose availability is being checked.
+     * @param locale The locale for which the availability is being checked.
+     * @return Indicates if always-on hotword detection is available for the given keyphrase.
+     * TODO(sansid): Unhide this.
+     * @hide
+     */
+    public final int getAlwaysOnKeyphraseAvailability(String keyphrase, String locale) {
+        // The available keyphrases is a combination of DSP availability and
+        // the keyphrases that have an enrollment application for them.
+        if (!mSoundTriggerManager.isKeyphraseSupported(keyphrase, locale)
+                || !mKeyphraseEnrollmentInfo.isKeyphraseEnrollmentSupported(keyphrase, locale)) {
+            return KEYPHRASE_UNAVAILABLE;
+        }
+        if (!mSoundTriggerManager.isKeyphraseEnrolled(keyphrase, locale)) {
+            return KEYPHRASE_UNENROLLED;
+        }
+        if (!mSoundTriggerManager.isKeyphraseActive(keyphrase, locale)) {
+            return KEYPHRASE_ENROLLED;
+        } else {
+            return KEYPHRASE_ACTIVE;
+        }
+    }
+
+    /**
+     * @return Details of keyphrases available for enrollment.
+     * @hide
+     */
+    @VisibleForTesting
+    protected final KeyphraseEnrollmentInfo getKeyphraseEnrollmentInfo() {
+        return mKeyphraseEnrollmentInfo;
+    }
 }
diff --git a/core/java/android/speech/tts/Markup.java b/core/java/android/speech/tts/Markup.java
new file mode 100644
index 0000000..c886e5d
--- /dev/null
+++ b/core/java/android/speech/tts/Markup.java
@@ -0,0 +1,537 @@
+package android.speech.tts;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class that provides markup to a synthesis request to control aspects of speech.
+ * <p>
+ * Markup itself is a feature agnostic data format; the {@link Utterance} class defines the currently
+ * available set of features and should be used to construct instances of the Markup class.
+ * </p>
+ * <p>
+ * A marked up sentence is a tree. Each node has a type, an optional plain text, a set of
+ * parameters, and a list of children.
+ * The <b>type</b> defines what it contains, e.g. "text", "date", "measure", etc. A Markup node
+ * can be either a part of sentence (often a leaf node), or node altering some property of its
+ * children (node with children). The top level node has to be of type "utterance" and its children
+ * are synthesized in order.
+ * The <b>plain text</b> is optional except for the top level node. If the synthesis engine does not
+ * support Markup at all, it should use the plain text of the top level node. If an engine does not
+ * recognize or support a node type, it will try to use the plain text of that node if provided. If
+ * the plain text is null, it will synthesize its children in order.
+ * <b>Parameters</b> are key-value pairs specific to each node type. In case of a date node the
+ * parameters may be for example "month: 7" and "day: 10".
+ * The <b>nested markups</b> are children and can for example be used to nest semiotic classes (a
+ * measure may have a node of type "decimal" as its child) or to modify some property of its
+ * children. See "plain text" on how they are processed if the parent of the children is unknown to
+ * the engine.
+ * <p>
+ */
+public final class Markup implements Parcelable {
+
+    private String mType;
+    private String mPlainText;
+
+    private Bundle mParameters = new Bundle();
+    private List<Markup> mNestedMarkups = new ArrayList<Markup>();
+
+    private static final String TYPE = "type";
+    private static final String PLAIN_TEXT = "plain_text";
+    private static final String MARKUP = "markup";
+
+    private static final String IDENTIFIER_REGEX = "([0-9a-z_]+)";
+    private static final Pattern legalIdentifierPattern = Pattern.compile(IDENTIFIER_REGEX);
+
+    /**
+     * Constructs an empty markup.
+     */
+    public Markup() {}
+
+    /**
+     * Constructs a markup of the given type.
+     */
+    public Markup(String type) {
+        setType(type);
+    }
+
+    /**
+     * Returns the type of this node; can be null.
+     */
+    public String getType() {
+        return mType;
+    }
+
+    /**
+     * Sets the type of this node. can be null. May only contain [0-9a-z_].
+     */
+    public void setType(String type) {
+        if (type != null) {
+            Matcher matcher = legalIdentifierPattern.matcher(type);
+            if (!matcher.matches()) {
+                throw new IllegalArgumentException("Type cannot be empty and may only contain " +
+                                                   "0-9, a-z and underscores.");
+            }
+        }
+        mType = type;
+    }
+
+    /**
+     * Returns this node's plain text; can be null.
+     */
+    public String getPlainText() {
+        return mPlainText;
+    }
+
+    /**
+     * Sets this nodes's plain text; can be null.
+     */
+    public void setPlainText(String plainText) {
+        mPlainText = plainText;
+    }
+
+    /**
+     * Adds or modifies a parameter.
+     * @param key The key; may only contain [0-9a-z_] and cannot be "type" or "plain_text".
+     * @param value The value.
+     * @throws An {@link IllegalArgumentException} if the key is null or empty.
+     * @return this
+     */
+    public Markup setParameter(String key, String value) {
+        if (key == null || key.isEmpty()) {
+            throw new IllegalArgumentException("Key cannot be null or empty.");
+        }
+        if (key.equals("type")) {
+            throw new IllegalArgumentException("Key cannot be \"type\".");
+        }
+        if (key.equals("plain_text")) {
+            throw new IllegalArgumentException("Key cannot be \"plain_text\".");
+        }
+        Matcher matcher = legalIdentifierPattern.matcher(key);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("Key may only contain 0-9, a-z and underscores.");
+        }
+
+        if (value != null) {
+            mParameters.putString(key, value);
+        } else {
+            removeParameter(key);
+        }
+        return this;
+    }
+
+    /**
+     * Removes the parameter with the given key
+     */
+    public void removeParameter(String key) {
+        mParameters.remove(key);
+    }
+
+    /**
+     * Returns the value of the parameter.
+     * @param key The parameter key.
+     * @return The value of the parameter or null if the parameter is not set.
+     */
+    public String getParameter(String key) {
+        return mParameters.getString(key);
+    }
+
+    /**
+     * Returns the number of parameters that have been set.
+     */
+    public int parametersSize() {
+        return mParameters.size();
+    }
+
+    /**
+     * Appends a child to the list of children
+     * @param markup The child.
+     * @return This instance.
+     * @throws {@link IllegalArgumentException} if markup is null.
+     */
+    public Markup addNestedMarkup(Markup markup) {
+        if (markup == null) {
+            throw new IllegalArgumentException("Nested markup cannot be null");
+        }
+        mNestedMarkups.add(markup);
+        return this;
+    }
+
+    /**
+     * Removes the given node from its children.
+     * @param markup The child to remove.
+     * @return True if this instance was modified by this operation, false otherwise.
+     */
+    public boolean removeNestedMarkup(Markup markup) {
+        return mNestedMarkups.remove(markup);
+    }
+
+    /**
+     * Returns the index'th child.
+     * @param i The index of the child.
+     * @return The child.
+     * @throws {@link IndexOutOfBoundsException} if i < 0 or i >= nestedMarkupSize()
+     */
+    public Markup getNestedMarkup(int i) {
+        return mNestedMarkups.get(i);
+    }
+
+
+    /**
+     * Returns the number of children.
+     */
+    public int nestedMarkupSize() {
+        return mNestedMarkups.size();
+    }
+
+    /**
+     * Returns a string representation of this Markup instance. Can be deserialized back to a Markup
+     * instance with markupFromString().
+     */
+    public String toString() {
+        StringBuilder out = new StringBuilder();
+        if (mType != null) {
+            out.append(TYPE + ": \"" + mType + "\"");
+        }
+        if (mPlainText != null) {
+            out.append(out.length() > 0 ? " " : "");
+            out.append(PLAIN_TEXT + ": \"" + escapeQuotedString(mPlainText) + "\"");
+        }
+        // Sort the parameters alphabetically by key so we have a stable output.
+        SortedMap<String, String> sortedMap = new TreeMap<String, String>();
+        for (String key : mParameters.keySet()) {
+            sortedMap.put(key, mParameters.getString(key));
+        }
+        for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
+            out.append(out.length() > 0 ? " " : "");
+            out.append(entry.getKey() + ": \"" + escapeQuotedString(entry.getValue()) + "\"");
+        }
+        for (Markup m : mNestedMarkups) {
+            out.append(out.length() > 0 ? " " : "");
+            String nestedStr = m.toString();
+            if (nestedStr.isEmpty()) {
+                out.append(MARKUP + " {}");
+            } else {
+                out.append(MARKUP + " { " + m.toString() + " }");
+            }
+        }
+        return out.toString();
+    }
+
+    /**
+     * Escapes backslashes and double quotes in the plain text and parameter values before this
+     * instance is written to a string.
+     * @param str The string to escape.
+     * @return The escaped string.
+     */
+    private static String escapeQuotedString(String str) {
+        StringBuilder out = new StringBuilder();
+        for (int i = 0; i < str.length(); i++) {
+            char c = str.charAt(i);
+            if (c == '"') {
+                out.append("\\\"");
+            } else if (str.charAt(i) == '\\') {
+                out.append("\\\\");
+            } else {
+                out.append(c);
+            }
+        }
+        return out.toString();
+    }
+
+    /**
+     * The reverse of the escape method, returning plain text and parameter values to their original
+     * form.
+     * @param str An escaped string.
+     * @return The unescaped string.
+     */
+    private static String unescapeQuotedString(String str) {
+        StringBuilder out = new StringBuilder();
+        for (int i = 0; i < str.length(); i++) {
+            char c = str.charAt(i);
+            if (c == '\\') {
+                i++;
+                if (i >= str.length()) {
+                    throw new IllegalArgumentException("Unterminated escape sequence in string: " +
+                                                       str);
+                }
+                c = str.charAt(i);
+                if (c == '\\') {
+                    out.append("\\");
+                } else if (c == '"') {
+                    out.append("\"");
+                } else {
+                    throw new IllegalArgumentException("Unsupported escape sequence: \\" + c +
+                                                       " in string " + str);
+                }
+            } else {
+                out.append(c);
+            }
+        }
+        return out.toString();
+    }
+
+    /**
+     * Returns true if the given string consists only of whitespace.
+     * @param str The string to check.
+     * @return True if the given string consists only of whitespace.
+     */
+    private static boolean isWhitespace(String str) {
+        return Pattern.matches("\\s*", str);
+    }
+
+    /**
+     * Parses the given string, and overrides the values of this instance with those contained
+     * in the given string.
+     * @param str The string to parse; can have superfluous whitespace.
+     * @return An empty string on success, else the remainder of the string that could not be
+     *     parsed.
+     */
+    private String fromReadableString(String str) {
+        while (!isWhitespace(str)) {
+            String newStr = matchValue(str);
+            if (newStr == null) {
+                newStr = matchMarkup(str);
+
+                if (newStr == null) {
+                    return str;
+                }
+            }
+            str = newStr;
+        }
+        return "";
+    }
+
+    // Matches: key : "value"
+    // where key is an identifier and value can contain escaped quotes
+    // there may be superflouous whitespace
+    // The value string may contain quotes and backslashes.
+    private static final String OPTIONAL_WHITESPACE = "\\s*";
+    private static final String VALUE_REGEX = "((\\\\.|[^\\\"])*)";
+    private static final String KEY_VALUE_REGEX =
+            "\\A" + OPTIONAL_WHITESPACE +                                         // start of string
+            IDENTIFIER_REGEX + OPTIONAL_WHITESPACE + ":" + OPTIONAL_WHITESPACE +  // key:
+            "\"" + VALUE_REGEX + "\"";                                            // "value"
+    private static final Pattern KEY_VALUE_PATTERN = Pattern.compile(KEY_VALUE_REGEX);
+
+    /**
+     * Tries to match a key-value pair at the start of the string. If found, add that as a parameter
+     * of this instance.
+     * @param str The string to parse.
+     * @return The remainder of the string without the parsed key-value pair on success, else null.
+     */
+    private String matchValue(String str) {
+        // Matches: key: "value"
+        Matcher matcher = KEY_VALUE_PATTERN.matcher(str);
+        if (!matcher.find()) {
+            return null;
+        }
+        String key = matcher.group(1);
+        String value = matcher.group(2);
+
+        if (key == null || value == null) {
+            return null;
+        }
+        String unescapedValue = unescapeQuotedString(value);
+        if (key.equals(TYPE)) {
+            this.mType = unescapedValue;
+        } else if (key.equals(PLAIN_TEXT)) {
+            this.mPlainText = unescapedValue;
+        } else {
+            setParameter(key, unescapedValue);
+        }
+
+        return str.substring(matcher.group(0).length());
+    }
+
+    // matches 'markup {'
+    private static final Pattern OPEN_MARKUP_PATTERN =
+            Pattern.compile("\\A" + OPTIONAL_WHITESPACE + MARKUP + OPTIONAL_WHITESPACE + "\\{");
+    // matches '}'
+    private static final Pattern CLOSE_MARKUP_PATTERN =
+            Pattern.compile("\\A" + OPTIONAL_WHITESPACE + "\\}");
+
+    /**
+     * Tries to parse a Markup specification from the start of the string. If so, add that markup to
+     * the list of nested Markup's of this instance.
+     * @param str The string to parse.
+     * @return The remainder of the string without the parsed Markup on success, else null.
+     */
+    private String matchMarkup(String str) {
+        // find and strip "markup {"
+        Matcher matcher = OPEN_MARKUP_PATTERN.matcher(str);
+
+        if (!matcher.find()) {
+            return null;
+        }
+        String strRemainder = str.substring(matcher.group(0).length());
+        // parse and strip markup contents
+        Markup nestedMarkup = new Markup();
+        strRemainder = nestedMarkup.fromReadableString(strRemainder);
+
+        // find and strip "}"
+        Matcher matcherClose = CLOSE_MARKUP_PATTERN.matcher(strRemainder);
+        if (!matcherClose.find()) {
+            return null;
+        }
+        strRemainder = strRemainder.substring(matcherClose.group(0).length());
+
+        // Everything parsed, add markup
+        this.addNestedMarkup(nestedMarkup);
+
+        // Return remainder
+        return strRemainder;
+    }
+
+    /**
+     * Returns a Markup instance from the string representation generated by toString().
+     * @param string The string representation generated by toString().
+     * @return The new Markup instance.
+     * @throws {@link IllegalArgumentException} if the input cannot be correctly parsed.
+     */
+    public static Markup markupFromString(String string) throws IllegalArgumentException {
+        Markup m = new Markup();
+        if (m.fromReadableString(string).isEmpty()) {
+            return m;
+        } else {
+            throw new IllegalArgumentException("Cannot parse input to Markup");
+        }
+    }
+
+    /**
+     * Compares the specified object with this Markup for equality.
+     * @return True if the given object is a Markup instance with the same type, plain text,
+     * parameters and the nested markups are also equal to each other and in the same order.
+     */
+    @Override
+    public boolean equals(Object o) {
+        if ( this == o ) return true;
+        if ( !(o instanceof Markup) ) return false;
+        Markup m = (Markup) o;
+
+        if (nestedMarkupSize() != this.nestedMarkupSize()) {
+            return false;
+        }
+
+        if (!(mType == null ? m.mType == null : mType.equals(m.mType))) {
+            return false;
+        }
+        if (!(mPlainText == null ? m.mPlainText == null : mPlainText.equals(m.mPlainText))) {
+            return false;
+        }
+        if (!equalBundles(mParameters, m.mParameters)) {
+            return false;
+        }
+
+        for (int i = 0; i < this.nestedMarkupSize(); i++) {
+            if (!mNestedMarkups.get(i).equals(m.mNestedMarkups.get(i))) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Checks if two bundles are equal to each other. Used by equals(o).
+     */
+    private boolean equalBundles(Bundle one, Bundle two) {
+        if (one == null || two == null) {
+            return false;
+        }
+
+        if(one.size() != two.size()) {
+            return false;
+        }
+
+        Set<String> valuesOne = one.keySet();
+        for(String key : valuesOne) {
+            Object valueOne = one.get(key);
+            Object valueTwo = two.get(key);
+            if (valueOne instanceof Bundle && valueTwo instanceof Bundle &&
+                !equalBundles((Bundle) valueOne, (Bundle) valueTwo)) {
+                return false;
+            } else if (valueOne == null) {
+                if (valueTwo != null || !two.containsKey(key)) {
+                    return false;
+                }
+            } else if(!valueOne.equals(valueTwo)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns an unmodifiable list of the children.
+     * @return An unmodifiable list of children that throws an {@link UnsupportedOperationException}
+     *     if an attempt is made to modify it
+     */
+    public List<Markup> getNestedMarkups() {
+        return Collections.unmodifiableList(mNestedMarkups);
+    }
+
+    /**
+     * @hide
+     */
+    public Markup(Parcel in) {
+        mType = in.readString();
+        mPlainText = in.readString();
+        mParameters = in.readBundle();
+        in.readList(mNestedMarkups, Markup.class.getClassLoader());
+    }
+
+    /**
+     * Creates a deep copy of the given markup.
+     */
+    public Markup(Markup markup) {
+        mType = markup.mType;
+        mPlainText = markup.mPlainText;
+        mParameters = markup.mParameters;
+        for (Markup nested : markup.getNestedMarkups()) {
+            addNestedMarkup(new Markup(nested));
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @hide
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mType);
+        dest.writeString(mPlainText);
+        dest.writeBundle(mParameters);
+        dest.writeList(mNestedMarkups);
+    }
+
+    /**
+     * @hide
+     */
+    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+        public Markup createFromParcel(Parcel in) {
+            return new Markup(in);
+        }
+
+        public Markup[] newArray(int size) {
+            return new Markup[size];
+        }
+    };
+}
+
diff --git a/core/java/android/speech/tts/RequestConfig.java b/core/java/android/speech/tts/RequestConfig.java
index 72109d3..84880c0 100644
--- a/core/java/android/speech/tts/RequestConfig.java
+++ b/core/java/android/speech/tts/RequestConfig.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
 package android.speech.tts;
 
 import android.media.AudioManager;
@@ -8,7 +23,6 @@
  *
  * This class is immutable, and can only be constructed using
  * {@link RequestConfig.Builder}.
- * @hide
  */
 public final class RequestConfig {
 
diff --git a/core/java/android/speech/tts/RequestConfigHelper.java b/core/java/android/speech/tts/RequestConfigHelper.java
index 2e548af..3b5490b 100644
--- a/core/java/android/speech/tts/RequestConfigHelper.java
+++ b/core/java/android/speech/tts/RequestConfigHelper.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
 package android.speech.tts;
 
 import android.speech.tts.TextToSpeechClient.EngineStatus;
@@ -7,7 +22,6 @@
 /**
  * Set of common heuristics for selecting {@link VoiceInfo} from
  * {@link TextToSpeechClient#getEngineStatus()} output.
- * @hide
  */
 public final class RequestConfigHelper {
     private RequestConfigHelper() {}
diff --git a/core/java/android/speech/tts/SynthesisCallback.java b/core/java/android/speech/tts/SynthesisCallback.java
index 7b319b7..bc2f239 100644
--- a/core/java/android/speech/tts/SynthesisCallback.java
+++ b/core/java/android/speech/tts/SynthesisCallback.java
@@ -43,13 +43,16 @@
      * request.
      *
      * This method should only be called on the synthesis thread,
-     * while in {@link TextToSpeechService#onSynthesizeText}.
+     * while in {@link TextToSpeechService#onSynthesizeText} or
+     * {@link TextToSpeechService#onSynthesizeTextV2}.
      *
      * @param sampleRateInHz Sample rate in HZ of the generated audio.
      * @param audioFormat Audio format of the generated audio. Must be one of
      *         the ENCODING_ constants defined in {@link android.media.AudioFormat}.
      * @param channelCount The number of channels. Must be {@code 1} or {@code 2}.
      * @return {@link TextToSpeech#SUCCESS}, {@link TextToSpeech#ERROR}.
+     *          {@link TextToSpeechClient.Status#STOPPED} is also possible if called in context of
+     *          {@link TextToSpeechService#onSynthesizeTextV2}.
      */
     public int start(int sampleRateInHz, int audioFormat, int channelCount);
 
@@ -57,7 +60,8 @@
      * The service should call this method when synthesized audio is ready for consumption.
      *
      * This method should only be called on the synthesis thread,
-     * while in {@link TextToSpeechService#onSynthesizeText}.
+     * while in {@link TextToSpeechService#onSynthesizeText} or
+     * {@link TextToSpeechService#onSynthesizeTextV2}.
      *
      * @param buffer The generated audio data. This method will not hold on to {@code buffer},
      *         so the caller is free to modify it after this method returns.
@@ -65,6 +69,8 @@
      * @param length The number of bytes of audio data in {@code buffer}. This must be
      *         less than or equal to the return value of {@link #getMaxBufferSize}.
      * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+     *          {@link TextToSpeechClient.Status#STOPPED} is also possible if called in context of
+     *          {@link TextToSpeechService#onSynthesizeTextV2}.
      */
     public int audioAvailable(byte[] buffer, int offset, int length);
 
@@ -73,11 +79,14 @@
      * been passed to {@link #audioAvailable}.
      *
      * This method should only be called on the synthesis thread,
-     * while in {@link TextToSpeechService#onSynthesizeText}.
+     * while in {@link TextToSpeechService#onSynthesizeText} or
+     * {@link TextToSpeechService#onSynthesizeTextV2}.
      *
      * This method has to be called if {@link #start} and/or {@link #error} was called.
      *
      * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+     *          {@link TextToSpeechClient.Status#STOPPED} is also possible if called in context of
+     *          {@link TextToSpeechService#onSynthesizeTextV2}.
      */
     public int done();
 
@@ -99,7 +108,6 @@
      *
      * @param errorCode Error code to pass to the client. One of the ERROR_ values from
      *      {@link TextToSpeechClient.Status}
-     * @hide
      */
     public void error(int errorCode);
 
@@ -120,7 +128,6 @@
      * @return {@link TextToSpeech#SUCCESS}, {@link TextToSpeech#ERROR} if client already
      *          called {@link #start(int, int, int)}, {@link TextToSpeechClient.Status#STOPPED}
      *          if stop was requested.
-     * @hide
      */
     public int fallback();
 
@@ -132,7 +139,6 @@
      * {@link TextToSpeechService#onSynthesizeTextV2}.
      *
      * Useful for checking if a fallback from network request is possible.
-     * @hide
      */
     public boolean hasStarted();
 
@@ -144,7 +150,6 @@
      * {@link TextToSpeechService#onSynthesizeTextV2}.
      *
      * Useful for checking if a fallback from network request is possible.
-     * @hide
      */
     public boolean hasFinished();
 }
diff --git a/core/java/android/speech/tts/SynthesisRequestV2.java b/core/java/android/speech/tts/SynthesisRequestV2.java
index beb7307..a42aa16 100644
--- a/core/java/android/speech/tts/SynthesisRequestV2.java
+++ b/core/java/android/speech/tts/SynthesisRequestV2.java
@@ -1,24 +1,42 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
 package android.speech.tts;
 
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.speech.tts.TextToSpeechClient.UtteranceId;
+import android.util.Log;
 
 /**
  * Service-side representation of a synthesis request from a V2 API client. Contains:
  * <ul>
- *   <li>The utterance to synthesize</li>
+ *   <li>The markup object to synthesize containing the utterance.</li>
  *   <li>The id of the utterance (String, result of {@link UtteranceId#toUniqueString()}</li>
  *   <li>The synthesis voice name (String, result of {@link VoiceInfo#getName()})</li>
  *   <li>Voice parameters (Bundle of parameters)</li>
  *   <li>Audio parameters (Bundle of parameters)</li>
  * </ul>
- * @hide
  */
 public final class SynthesisRequestV2 implements Parcelable {
-    /** Synthesis utterance. */
-    private final String mText;
+
+    private static final String TAG = "SynthesisRequestV2";
+
+    /** Synthesis markup */
+    private final Markup mMarkup;
 
     /** Synthesis id. */
     private final String mUtteranceId;
@@ -35,9 +53,9 @@
     /**
      * Constructor for test purposes.
      */
-    public SynthesisRequestV2(String text, String utteranceId, String voiceName,
+    public SynthesisRequestV2(Markup markup, String utteranceId, String voiceName,
             Bundle voiceParams, Bundle audioParams) {
-        this.mText = text;
+        this.mMarkup = markup;
         this.mUtteranceId = utteranceId;
         this.mVoiceName = voiceName;
         this.mVoiceParams = voiceParams;
@@ -50,15 +68,18 @@
      * @hide
      */
     public SynthesisRequestV2(Parcel in) {
-        this.mText = in.readString();
+        this.mMarkup = (Markup) in.readValue(Markup.class.getClassLoader());
         this.mUtteranceId = in.readString();
         this.mVoiceName = in.readString();
         this.mVoiceParams = in.readBundle();
         this.mAudioParams = in.readBundle();
     }
 
-    SynthesisRequestV2(String text, String utteranceId, RequestConfig rconfig) {
-        this.mText = text;
+    /**
+     * Constructor to request the synthesis of a sentence.
+     */
+    SynthesisRequestV2(Markup markup, String utteranceId, RequestConfig rconfig) {
+        this.mMarkup = markup;
         this.mUtteranceId = utteranceId;
         this.mVoiceName = rconfig.getVoice().getName();
         this.mVoiceParams = rconfig.getVoiceParams();
@@ -72,7 +93,7 @@
      */
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(mText);
+        dest.writeValue(mMarkup);
         dest.writeString(mUtteranceId);
         dest.writeString(mVoiceName);
         dest.writeBundle(mVoiceParams);
@@ -83,7 +104,18 @@
      * @return the text which should be synthesized.
      */
     public String getText() {
-        return mText;
+        if (mMarkup.getPlainText() == null) {
+            Log.e(TAG, "Plaintext of markup is null.");
+            return "";
+        }
+        return mMarkup.getPlainText();
+    }
+
+    /**
+     * @return the markup which should be synthesized.
+     */
+    public Markup getMarkup() {
+        return mMarkup;
     }
 
     /**
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index cc86ed7..c527acf 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -53,7 +53,10 @@
  * notified of the completion of the initialization.<br>
  * When you are done using the TextToSpeech instance, call the {@link #shutdown()} method
  * to release the native resources used by the TextToSpeech engine.
+ *
+ * @deprecated Use {@link TextToSpeechClient} instead
  */
+@Deprecated
 public class TextToSpeech {
 
     private static final String TAG = "TextToSpeech";
diff --git a/core/java/android/speech/tts/TextToSpeechClient.java b/core/java/android/speech/tts/TextToSpeechClient.java
index 2a24229..0c0be83 100644
--- a/core/java/android/speech/tts/TextToSpeechClient.java
+++ b/core/java/android/speech/tts/TextToSpeechClient.java
@@ -71,7 +71,6 @@
  * In the rare case of a change to the set of available voices, the service will call to the
  * {@link ConnectionCallbacks#onEngineStatusChange} with new set of available voices as argument.
  * In response, the client HAVE to recreate all {@link RequestConfig} instances in use.
- * @hide
  */
 public class TextToSpeechClient {
     private static final String TAG = TextToSpeechClient.class.getSimpleName();
@@ -513,7 +512,6 @@
         }
     }
 
-
     /**
      * Connects the client to TTS service. This method returns immediately, and connects to the
      * service in the background.
@@ -795,15 +793,14 @@
             return mService != null && mEstablished;
         }
 
-        boolean runAction(Action action) {
+        <T> ActionResult<T> runAction(Action<T> action) {
             synchronized (mLock) {
                 try {
-                    action.run(mService);
-                    return true;
+                    return new ActionResult<T>(true, action.run(mService));
                 } catch (Exception ex) {
                     Log.e(TAG, action.getName() + " failed", ex);
                     disconnect();
-                    return false;
+                    return new ActionResult<T>(false);
                 }
             }
         }
@@ -823,7 +820,7 @@
         }
     }
 
-    private abstract class Action {
+    private abstract class Action<T> {
         private final String mName;
 
         public Action(String name) {
@@ -831,7 +828,21 @@
         }
 
         public String getName() {return mName;}
-        abstract void run(ITextToSpeechService service) throws RemoteException;
+        abstract T run(ITextToSpeechService service) throws RemoteException;
+    }
+
+    private class ActionResult<T> {
+        boolean mSuccess;
+        T mResult;
+
+        ActionResult(boolean success) {
+            mSuccess = success;
+        }
+
+        ActionResult(boolean success, T result) {
+            mSuccess = success;
+            mResult = result;
+        }
     }
 
     private IBinder getCallerIdentity() {
@@ -841,18 +852,17 @@
         return null;
     }
 
-    private boolean runAction(Action action) {
+    private <T> ActionResult<T> runAction(Action<T> action) {
         synchronized (mLock) {
             if (mServiceConnection == null) {
                 Log.w(TAG, action.getName() + " failed: not bound to TTS engine");
-                return false;
+                return new ActionResult<T>(false);
             }
             if (!mServiceConnection.isEstablished()) {
                 Log.w(TAG, action.getName() + " failed: not fully bound to TTS engine");
-                return false;
+                return new ActionResult<T>(false);
             }
-            mServiceConnection.runAction(action);
-            return true;
+            return mServiceConnection.runAction(action);
         }
     }
 
@@ -863,13 +873,14 @@
      * other utterances in the queue.
      */
     public void stop() {
-        runAction(new Action(ACTION_STOP_NAME) {
+        runAction(new Action<Void>(ACTION_STOP_NAME) {
             @Override
-            public void run(ITextToSpeechService service) throws RemoteException {
+            public Void run(ITextToSpeechService service) throws RemoteException {
                if (service.stop(getCallerIdentity()) != Status.SUCCESS) {
                    Log.e(TAG, "Stop failed");
                }
                mCallbacks.clear();
+               return null;
             }
         });
     }
@@ -877,7 +888,7 @@
     private static final String ACTION_QUEUE_SPEAK_NAME = "queueSpeak";
 
     /**
-     * Speaks the string using the specified queuing strategy using current
+     * Speaks the string using the specified queuing strategy and the current
      * voice. This method is asynchronous, i.e. the method just adds the request
      * to the queue of TTS requests and then returns. The synthesis might not
      * have finished (or even started!) at the time when this method returns.
@@ -888,15 +899,38 @@
      *            in {@link RequestCallbacks}.
      * @param config Synthesis request configuration. Can't be null. Has to contain a
      *            voice.
-     * @param callbacks Synthesis request callbacks. If null, default request
+     * @param callbacks Synthesis request callbacks. If null, the default request
      *            callbacks object will be used.
      */
     public void queueSpeak(final String utterance, final UtteranceId utteranceId,
             final RequestConfig config,
             final RequestCallbacks callbacks) {
-        runAction(new Action(ACTION_QUEUE_SPEAK_NAME) {
+        queueSpeak(createMarkupFromString(utterance), utteranceId, config, callbacks);
+    }
+
+    /**
+     * Speaks the {@link Markup} (which can be constructed with {@link Utterance}) using
+     * the specified queuing strategy and the current voice. This method is
+     * asynchronous, i.e. the method just adds the request to the queue of TTS
+     * requests and then returns. The synthesis might not have finished (or even
+     * started!) at the time when this method returns.
+     *
+     * @param markup The Markup to be spoken. The written equivalent of the spoken
+     *            text should be no longer than 1000 characters.
+     * @param utteranceId Unique identificator used to track the synthesis progress
+     *            in {@link RequestCallbacks}.
+     * @param config Synthesis request configuration. Can't be null. Has to contain a
+     *            voice.
+     * @param callbacks Synthesis request callbacks. If null, the default request
+     *            callbacks object will be used.
+     */
+    public void queueSpeak(final Markup markup,
+            final UtteranceId utteranceId,
+            final RequestConfig config,
+            final RequestCallbacks callbacks) {
+        runAction(new Action<Void>(ACTION_QUEUE_SPEAK_NAME) {
             @Override
-            public void run(ITextToSpeechService service) throws RemoteException {
+            public Void run(ITextToSpeechService service) throws RemoteException {
                 RequestCallbacks c = mDefaultRequestCallbacks;
                 if (callbacks != null) {
                     c = callbacks;
@@ -904,15 +938,16 @@
                 int addCallbackStatus = addCallback(utteranceId, c);
                 if (addCallbackStatus != Status.SUCCESS) {
                     c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST);
-                    return;
+                    return null;
                 }
 
                 int queueResult = service.speakV2(
                         getCallerIdentity(),
-                        new SynthesisRequestV2(utterance, utteranceId.toUniqueString(), config));
+                        new SynthesisRequestV2(markup, utteranceId.toUniqueString(), config));
                 if (queueResult != Status.SUCCESS) {
                     removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
                 }
+                return null;
             }
         });
     }
@@ -932,15 +967,40 @@
      * @param outputFile File to write the generated audio data to.
      * @param config Synthesis request configuration. Can't be null. Have to contain a
      *            voice.
-     * @param callbacks Synthesis request callbacks. If null, default request
+     * @param callbacks Synthesis request callbacks. If null, the default request
      *            callbacks object will be used.
      */
     public void queueSynthesizeToFile(final String utterance, final UtteranceId utteranceId,
             final File outputFile, final RequestConfig config,
             final RequestCallbacks callbacks) {
-        runAction(new Action(ACTION_QUEUE_SYNTHESIZE_TO_FILE) {
+        queueSynthesizeToFile(createMarkupFromString(utterance), utteranceId, outputFile, config, callbacks);
+    }
+
+    /**
+     * Synthesizes the given {@link Markup} (can be constructed with {@link Utterance})
+     * to a file using the specified parameters. This method is asynchronous, i.e. the
+     * method just adds the request to the queue of TTS requests and then returns. The
+     * synthesis might not have finished (or even started!) at the time when this method
+     * returns.
+     *
+     * @param markup The Markup that should be synthesized. The written equivalent of
+     *            the spoken text should be no longer than 1000 characters.
+     * @param utteranceId Unique identificator used to track the synthesis progress
+     *            in {@link RequestCallbacks}.
+     * @param outputFile File to write the generated audio data to.
+     * @param config Synthesis request configuration. Can't be null. Have to contain a
+     *            voice.
+     * @param callbacks Synthesis request callbacks. If null, the default request
+     *            callbacks object will be used.
+     */
+    public void queueSynthesizeToFile(
+            final Markup markup,
+            final UtteranceId utteranceId,
+            final File outputFile, final RequestConfig config,
+            final RequestCallbacks callbacks) {
+        runAction(new Action<Void>(ACTION_QUEUE_SYNTHESIZE_TO_FILE) {
             @Override
-            public void run(ITextToSpeechService service) throws RemoteException {
+            public Void run(ITextToSpeechService service) throws RemoteException {
                 RequestCallbacks c = mDefaultRequestCallbacks;
                 if (callbacks != null) {
                     c = callbacks;
@@ -948,7 +1008,7 @@
                 int addCallbackStatus = addCallback(utteranceId, c);
                 if (addCallbackStatus != Status.SUCCESS) {
                     c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST);
-                    return;
+                    return null;
                 }
 
                 ParcelFileDescriptor fileDescriptor = null;
@@ -956,7 +1016,7 @@
                     if (outputFile.exists() && !outputFile.canWrite()) {
                         Log.e(TAG, "No permissions to write to " + outputFile);
                         removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT);
-                        return;
+                        return null;
                     }
                     fileDescriptor = ParcelFileDescriptor.open(outputFile,
                             ParcelFileDescriptor.MODE_WRITE_ONLY |
@@ -965,8 +1025,7 @@
 
                     int queueResult = service.synthesizeToFileDescriptorV2(getCallerIdentity(),
                             fileDescriptor,
-                            new SynthesisRequestV2(utterance, utteranceId.toUniqueString(),
-                                    config));
+                            new SynthesisRequestV2(markup, utteranceId.toUniqueString(), config));
                     fileDescriptor.close();
                     if (queueResult != Status.SUCCESS) {
                         removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
@@ -978,10 +1037,18 @@
                     Log.e(TAG, "Closing file " + outputFile + " failed", e);
                     removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT);
                 }
+                return null;
             }
         });
     }
 
+    private static Markup createMarkupFromString(String str) {
+        return new Utterance()
+            .append(new Utterance.TtsText(str))
+            .setNoWarningOnFallback(true)
+            .createMarkup();
+    }
+
     private static final String ACTION_QUEUE_SILENCE_NAME = "queueSilence";
 
     /**
@@ -998,9 +1065,9 @@
      */
     public void queueSilence(final long durationInMs, final UtteranceId utteranceId,
             final RequestCallbacks callbacks) {
-        runAction(new Action(ACTION_QUEUE_SILENCE_NAME) {
+        runAction(new Action<Void>(ACTION_QUEUE_SILENCE_NAME) {
             @Override
-            public void run(ITextToSpeechService service) throws RemoteException {
+            public Void run(ITextToSpeechService service) throws RemoteException {
                 RequestCallbacks c = mDefaultRequestCallbacks;
                 if (callbacks != null) {
                     c = callbacks;
@@ -1016,6 +1083,7 @@
                 if (queueResult != Status.SUCCESS) {
                     removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
                 }
+                return null;
             }
         });
     }
@@ -1039,9 +1107,9 @@
      */
     public void queueAudio(final Uri audioUrl, final UtteranceId utteranceId,
             final RequestConfig config, final RequestCallbacks callbacks) {
-        runAction(new Action(ACTION_QUEUE_AUDIO_NAME) {
+        runAction(new Action<Void>(ACTION_QUEUE_AUDIO_NAME) {
             @Override
-            public void run(ITextToSpeechService service) throws RemoteException {
+            public Void run(ITextToSpeechService service) throws RemoteException {
                 RequestCallbacks c = mDefaultRequestCallbacks;
                 if (callbacks != null) {
                     c = callbacks;
@@ -1057,10 +1125,35 @@
                 if (queueResult != Status.SUCCESS) {
                     removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
                 }
+                return null;
             }
         });
     }
 
+    private static final String ACTION_IS_SPEAKING_NAME = "isSpeaking";
+
+    /**
+     * Checks whether the TTS engine is busy speaking. Note that a speech item is
+     * considered complete once it's audio data has been sent to the audio mixer, or
+     * written to a file. There might be a finite lag between this point, and when
+     * the audio hardware completes playback.
+     *
+     * @return {@code true} if the TTS engine is speaking.
+     */
+    public boolean isSpeaking() {
+        ActionResult<Boolean> result = runAction(new Action<Boolean>(ACTION_IS_SPEAKING_NAME) {
+            @Override
+            public Boolean run(ITextToSpeechService service) throws RemoteException {
+                return service.isSpeaking();
+            }
+        });
+        if (!result.mSuccess) {
+            return false; // We can't really say, return false
+        }
+        return result.mResult;
+    }
+
+
     class InternalHandler extends Handler {
         final static int WHAT_ENGINE_STATUS_CHANGED = 1;
         final static int WHAT_SERVICE_DISCONNECTED = 2;
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index a728472..14a4024 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -74,6 +74,26 @@
  *
  * {@link #onGetLanguage} is not required as of JELLYBEAN_MR2 (API 18) and later, it is only
  * called on earlier versions of Android.
+ * <p>
+ * In order to fully support the V2 API ({@link TextToSpeechClient}),
+ * these methods must be implemented:
+ * <ul>
+ * <li>{@link #onSynthesizeTextV2}</li>
+ * <li>{@link #checkVoicesInfo}</li>
+ * <li>{@link #onVoicesInfoChange}</li>
+ * <li>{@link #implementsV2API}</li>
+ * </ul>
+ * In addition {@link #implementsV2API} has to return true.
+ * <p>
+ * If the service does not implement these methods and {@link #implementsV2API} returns false,
+ * then the V2 API will be provided by converting V2 requests ({@link #onSynthesizeTextV2})
+ * to V1 requests ({@link #onSynthesizeText}). On service setup, all of the available device
+ * locales will be fed to {@link #onIsLanguageAvailable} to check if they are supported.
+ * If they are, embedded and/or network voices will be created depending on the result of
+ * {@link #onGetFeaturesForLanguage}.
+ * <p>
+ * Note that a V2 service will still receive requests from V1 clients and has to implement all
+ * of the V1 API methods.
  */
 public abstract class TextToSpeechService extends Service {
 
@@ -225,7 +245,6 @@
      * The result of this method will be saved and served to all TTS clients. If a TTS service wants
      * to update the set of available voices, it should call the {@link #forceVoicesInfoCheck()}
      * method.
-     * @hide
      */
     protected List<VoiceInfo> checkVoicesInfo() {
         if (implementsV2API()) {
@@ -293,7 +312,6 @@
      * Tells the synthesis thread that it should reload voice data.
      * There's a high probability that the underlying set of available voice data has changed.
      * Called only on the synthesis thread.
-     * @hide
      */
     protected void onVoicesInfoChange() {
 
@@ -308,7 +326,6 @@
      * @param request The synthesis request.
      * @param callback The callback the the engine must use to make data
      *            available for playback or for writing to a file.
-     * @hide
      */
     protected void onSynthesizeTextV2(SynthesisRequestV2 request,
             VoiceInfo selectedVoice,
@@ -335,6 +352,12 @@
             params.putString(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS, "true");
         }
 
+        String noWarning = request.getMarkup().getParameter(Utterance.KEY_NO_WARNING_ON_FALLBACK);
+        if (noWarning == null || noWarning.equals("false")) {
+            Log.w("TextToSpeechService", "The synthesis engine does not support Markup, falling " +
+                                         "back to the given plain text.");
+        }
+
         // Build V1 request
         SynthesisRequest requestV1 = new SynthesisRequest(request.getText(), params);
         Locale locale = selectedVoice.getLocale();
@@ -350,7 +373,6 @@
     /**
      * If true, this service implements proper V2 TTS API service. If it's false,
      * V2 API will be provided through adapter.
-     * @hide
      */
     protected boolean implementsV2API() {
         return false;
@@ -389,9 +411,6 @@
         }
     }
 
-   /**
-    * @hide
-    */
     public VoiceInfo getVoicesInfoWithName(String name) {
         synchronized (mVoicesInfoLock) {
             if (mVoicesInfoLookup != null) {
@@ -411,7 +430,6 @@
      * Use this method only if you know that set of available languages changed.
      *
      * Can be called on multiple threads.
-     * @hide
      */
     public void forceVoicesInfoCheck() {
         synchronized (mVoicesInfoLock) {
@@ -844,14 +862,53 @@
             }
         }
 
+        /**
+         * Estimate of the character count equivalent of a Markup instance. Calculated
+         * by summing the characters of all Markups of type "text". Each other node
+         * is counted as a single character, as the character count of other nodes
+         * is non-trivial to calculate and we don't want to accept arbitrarily large
+         * requests.
+         */
+        private int estimateSynthesisLengthFromMarkup(Markup m) {
+            int size = 0;
+            if (m.getType() != null &&
+                m.getType().equals("text") &&
+                m.getParameter("text") != null) {
+                size += m.getParameter("text").length();
+            } else if (m.getType() == null ||
+                       !m.getType().equals("utterance")) {
+                size += 1;
+            }
+            for (Markup nested : m.getNestedMarkups()) {
+                size += estimateSynthesisLengthFromMarkup(nested);
+            }
+            return size;
+        }
+
         @Override
         public boolean isValid() {
-            if (mSynthesisRequest.getText() == null) {
-                Log.e(TAG, "null synthesis text");
+            if (mSynthesisRequest.getMarkup() == null) {
+                Log.e(TAG, "No markup in request.");
                 return false;
             }
-            if (mSynthesisRequest.getText().length() >= TextToSpeech.getMaxSpeechInputLength()) {
-                Log.w(TAG, "Text too long: " + mSynthesisRequest.getText().length() + " chars");
+            String type = mSynthesisRequest.getMarkup().getType();
+            if (type == null) {
+                Log.w(TAG, "Top level markup node should have type \"utterance\", not null");
+                return false;
+            } else if (!type.equals("utterance")) {
+                Log.w(TAG, "Top level markup node should have type \"utterance\" instead of " +
+                            "\"" + type + "\"");
+                return false;
+            }
+
+            int estimate = estimateSynthesisLengthFromMarkup(mSynthesisRequest.getMarkup());
+            if (estimate >= TextToSpeech.getMaxSpeechInputLength()) {
+                Log.w(TAG, "Text too long: estimated size of text was " + estimate + " chars.");
+                return false;
+            }
+
+            if (estimate <= 0) {
+                Log.e(TAG, "null synthesis text");
                 return false;
             }
 
diff --git a/core/java/android/speech/tts/Utterance.java b/core/java/android/speech/tts/Utterance.java
new file mode 100644
index 0000000..0a29283
--- /dev/null
+++ b/core/java/android/speech/tts/Utterance.java
@@ -0,0 +1,595 @@
+package android.speech.tts;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class acts as a builder for {@link Markup} instances.
+ * <p>
+ * Each Utterance consists of a list of the semiotic classes ({@link Utterance.TtsCardinal} and
+ * {@link Utterance.TtsText}).
+ * <p>Each semiotic class can be supplied with morphosyntactic features
+ * (gender, animacy, multiplicity and case), it is up to the synthesis engine to use this
+ * information during synthesis.
+ * Examples where morphosyntactic features matter:
+ * <ul>
+ * <li>In French, the number one is verbalized differently based on the gender of the noun
+ * it modifies. "un homme" (one man) versus "une femme" (one woman).
+ * <li>In German the grammatical case (accusative, locative, etc) needs to be included to be
+ * verbalize correctly. In German you'd have the sentence "Sie haben 1 kilometer vor Ihnen" (You
+ * have 1 kilometer ahead of you), "1" in this case needs to become inflected to the accusative
+ * form ("einen") instead of the nominative form "ein".
+ * </p>
+ * <p>
+ * Utterance usage example:
+ * Markup m1 = new Utterance().append("The Eiffel Tower is")
+ *                            .append(new TtsCardinal(324))
+ *                            .append("meters tall.");
+ * Markup m2 = new Utterance().append("Sie haben")
+ *                            .append(new TtsCardinal(1).setGender(Utterance.GENDER_MALE)
+ *                            .append("Tag frei.");
+ * </p>
+ */
+public class Utterance {
+
+    /***
+     * Toplevel type of markup representation.
+     */
+    public static final String TYPE_UTTERANCE = "utterance";
+    /***
+     * The no_warning_on_fallback parameter can be set to "false" or "true", true indicating that
+     * no warning will be given when the synthesizer does not support Markup. This is used when
+     * the user only provides a string to the API instead of a markup.
+     */
+    public static final String KEY_NO_WARNING_ON_FALLBACK = "no_warning_on_fallback";
+
+    // Gender.
+    public final static int GENDER_UNKNOWN = 0;
+    public final static int GENDER_NEUTRAL = 1;
+    public final static int GENDER_MALE = 2;
+    public final static int GENDER_FEMALE = 3;
+
+    // Animacy.
+    public final static int ANIMACY_UNKNOWN = 0;
+    public final static int ANIMACY_ANIMATE = 1;
+    public final static int ANIMACY_INANIMATE = 2;
+
+    // Multiplicity.
+    public final static int MULTIPLICITY_UNKNOWN = 0;
+    public final static int MULTIPLICITY_SINGLE = 1;
+    public final static int MULTIPLICITY_DUAL = 2;
+    public final static int MULTIPLICITY_PLURAL = 3;
+
+    // Case.
+    public final static int CASE_UNKNOWN = 0;
+    public final static int CASE_NOMINATIVE = 1;
+    public final static int CASE_ACCUSATIVE = 2;
+    public final static int CASE_DATIVE = 3;
+    public final static int CASE_ABLATIVE = 4;
+    public final static int CASE_GENITIVE = 5;
+    public final static int CASE_VOCATIVE = 6;
+    public final static int CASE_LOCATIVE = 7;
+    public final static int CASE_INSTRUMENTAL = 8;
+
+    private List<AbstractTts<? extends AbstractTts<?>>> says =
+            new ArrayList<AbstractTts<? extends AbstractTts<?>>>();
+    Boolean mNoWarningOnFallback = null;
+
+    /**
+     * Objects deriving from this class can be appended to a Utterance. This class uses generics
+     * so method from this class can return instances of its child classes, resulting in a better
+     * API (CRTP pattern).
+     */
+    public static abstract class AbstractTts<C extends AbstractTts<C>> {
+
+        protected Markup mMarkup = new Markup();
+
+        /**
+         * Empty constructor.
+         */
+        protected AbstractTts() {
+        }
+
+        /**
+         * Construct with Markup.
+         * @param markup
+         */
+        protected AbstractTts(Markup markup) {
+            mMarkup = markup;
+        }
+
+        /**
+         * Returns the type of this class, e.g. "cardinal" or "measure".
+         * @return The type.
+         */
+        public String getType() {
+            return mMarkup.getType();
+        }
+
+        /**
+         * A fallback plain text can be provided, in case the engine does not support this class
+         * type, or even Markup altogether.
+         * @param plainText A string with the plain text.
+         * @return This instance.
+         */
+        @SuppressWarnings("unchecked")
+        public C setPlainText(String plainText) {
+            mMarkup.setPlainText(plainText);
+            return (C) this;
+        }
+
+        /**
+         * Returns the plain text (fallback) string.
+         * @return Plain text string or null if not set.
+         */
+        public String getPlainText() {
+            return mMarkup.getPlainText();
+        }
+
+        /**
+         * Populates the plainText if not set and builds a Markup instance.
+         * @return The Markup object describing this instance.
+         */
+        public Markup getMarkup() {
+            return new Markup(mMarkup);
+        }
+
+        @SuppressWarnings("unchecked")
+        protected C setParameter(String key, String value) {
+            mMarkup.setParameter(key, value);
+            return (C) this;
+        }
+
+        protected String getParameter(String key) {
+            return mMarkup.getParameter(key);
+        }
+
+        @SuppressWarnings("unchecked")
+        protected C removeParameter(String key) {
+            mMarkup.removeParameter(key);
+            return (C) this;
+        }
+
+        /**
+         * Returns a string representation of this instance, can be deserialized to an equal
+         * Utterance instance.
+         */
+        public String toString() {
+            return mMarkup.toString();
+        }
+
+        /**
+         * Returns a generated plain text alternative for this instance if this instance isn't
+         * better representated by the list of it's children.
+         * @return Best effort plain text representation of this instance, can be null.
+         */
+        public String generatePlainText() {
+            return null;
+        }
+    }
+
+    public static abstract class AbstractTtsSemioticClass<C extends AbstractTtsSemioticClass<C>>
+            extends AbstractTts<C> {
+        // Keys.
+        private static final String KEY_GENDER = "gender";
+        private static final String KEY_ANIMACY = "animacy";
+        private static final String KEY_MULTIPLICITY = "multiplicity";
+        private static final String KEY_CASE = "case";
+
+        protected AbstractTtsSemioticClass() {
+            super();
+        }
+
+        protected AbstractTtsSemioticClass(Markup markup) {
+            super(markup);
+        }
+
+        @SuppressWarnings("unchecked")
+        public C setGender(int gender) {
+            if (gender < 0 || gender > 3) {
+                throw new IllegalArgumentException("Only four types of gender can be set: " +
+                                                   "unknown, neutral, maculine and female.");
+            }
+            if (gender != GENDER_UNKNOWN) {
+                setParameter(KEY_GENDER, String.valueOf(gender));
+            } else {
+                setParameter(KEY_GENDER, null);
+            }
+            return (C) this;
+        }
+
+        public int getGender() {
+            String gender = mMarkup.getParameter(KEY_GENDER);
+            return gender != null ? Integer.valueOf(gender) : GENDER_UNKNOWN;
+        }
+
+        @SuppressWarnings("unchecked")
+        public C setAnimacy(int animacy) {
+            if (animacy < 0 || animacy > 2) {
+                throw new IllegalArgumentException(
+                        "Only two types of animacy can be set: unknown, animate and inanimate");
+            }
+            if (animacy != ANIMACY_UNKNOWN) {
+                setParameter(KEY_ANIMACY, String.valueOf(animacy));
+            } else {
+                setParameter(KEY_ANIMACY, null);
+            }
+            return (C) this;
+        }
+
+        public int getAnimacy() {
+            String animacy = getParameter(KEY_ANIMACY);
+            return animacy != null ? Integer.valueOf(animacy) : ANIMACY_UNKNOWN;
+        }
+
+        @SuppressWarnings("unchecked")
+        public C setMultiplicity(int multiplicity) {
+            if (multiplicity < 0 || multiplicity > 3) {
+                throw new IllegalArgumentException(
+                        "Only four types of multiplicity can be set: unknown, single, dual and " +
+                        "plural.");
+            }
+            if (multiplicity != MULTIPLICITY_UNKNOWN) {
+                setParameter(KEY_MULTIPLICITY, String.valueOf(multiplicity));
+            } else {
+                setParameter(KEY_MULTIPLICITY, null);
+            }
+            return (C) this;
+        }
+
+        public int getMultiplicity() {
+            String multiplicity = mMarkup.getParameter(KEY_MULTIPLICITY);
+            return multiplicity != null ? Integer.valueOf(multiplicity) : MULTIPLICITY_UNKNOWN;
+        }
+
+        @SuppressWarnings("unchecked")
+        public C setCase(int grammaticalCase) {
+            if (grammaticalCase < 0 || grammaticalCase > 8) {
+                throw new IllegalArgumentException(
+                        "Only nine types of grammatical case can be set.");
+            }
+            if (grammaticalCase != CASE_UNKNOWN) {
+                setParameter(KEY_CASE, String.valueOf(grammaticalCase));
+            } else {
+                setParameter(KEY_CASE, null);
+            }
+            return (C) this;
+        }
+
+        public int getCase() {
+            String grammaticalCase = mMarkup.getParameter(KEY_CASE);
+            return grammaticalCase != null ? Integer.valueOf(grammaticalCase) : CASE_UNKNOWN;
+        }
+    }
+
+    /**
+     * Class that contains regular text, synthesis engine pronounces it using its regular pipeline.
+     * Parameters:
+     * <ul>
+     *   <li>Text: the text to synthesize</li>
+     * </ul>
+     */
+    public static class TtsText extends AbstractTtsSemioticClass<TtsText> {
+
+        // The type of this node.
+        protected static final String TYPE_TEXT = "text";
+        // The text parameter stores the text to be synthesized.
+        private static final String KEY_TEXT = "text";
+
+        /**
+         * Default constructor.
+         */
+        public TtsText() {
+            mMarkup.setType(TYPE_TEXT);
+        }
+
+        /**
+         * Constructor that sets the text to be synthesized.
+         * @param text The text to be synthesized.
+         */
+        public TtsText(String text) {
+            this();
+            setText(text);
+        }
+
+        /**
+         * Constructs a TtsText with the values of the Markup, does not check if the given Markup is
+         * of the right type.
+         */
+        private TtsText(Markup markup) {
+            super(markup);
+        }
+
+        /**
+         * Sets the text to be synthesized.
+         * @return This instance.
+         */
+        public TtsText setText(String text) {
+            setParameter(KEY_TEXT, text);
+            return this;
+        }
+
+        /**
+         * Returns the text to be synthesized.
+         * @return This instance.
+         */
+        public String getText() {
+            return getParameter(KEY_TEXT);
+        }
+
+        /**
+         * Generates a best effort plain text, in this case simply the text.
+         */
+        @Override
+        public String generatePlainText() {
+            return getText();
+        }
+    }
+
+    /**
+     * Contains a cardinal.
+     * Parameters:
+     * <ul>
+     *   <li>integer: the integer to synthesize</li>
+     * </ul>
+     */
+    public static class TtsCardinal extends AbstractTtsSemioticClass<TtsCardinal> {
+
+        // The type of this node.
+        protected static final String TYPE_CARDINAL = "cardinal";
+        // The parameter integer stores the integer to synthesize.
+        private static final String KEY_INTEGER = "integer";
+
+        /**
+         * Default constructor.
+         */
+        public TtsCardinal() {
+            mMarkup.setType(TYPE_CARDINAL);
+        }
+
+        /**
+         * Constructor that sets the integer to be synthesized.
+         */
+        public TtsCardinal(int integer) {
+            this();
+            setInteger(integer);
+        }
+
+        /**
+         * Constructor that sets the integer to be synthesized.
+         */
+        public TtsCardinal(String integer) {
+            this();
+            setInteger(integer);
+        }
+
+        /**
+         * Constructs a TtsText with the values of the Markup.
+         * Does not check if the given Markup is of the right type.
+         */
+        private TtsCardinal(Markup markup) {
+            super(markup);
+        }
+
+        /**
+         * Sets the integer.
+         * @return This instance.
+         */
+        public TtsCardinal setInteger(int integer) {
+            return setInteger(String.valueOf(integer));
+        }
+
+        /**
+         * Sets the integer.
+         * @param integer A non-empty string of digits with an optional '-' in front.
+         * @return This instance.
+         */
+        public TtsCardinal setInteger(String integer) {
+            if (!integer.matches("-?\\d+")) {
+                throw new IllegalArgumentException("Expected a cardinal: \"" + integer + "\"");
+            }
+            setParameter(KEY_INTEGER, integer);
+            return this;
+        }
+
+        /**
+         * Returns the integer parameter.
+         */
+        public String getInteger() {
+            return getParameter(KEY_INTEGER);
+        }
+
+        /**
+         * Generates a best effort plain text, in this case simply the integer.
+         */
+        @Override
+        public String generatePlainText() {
+            return getInteger();
+        }
+    }
+
+    /**
+     * Default constructor.
+     */
+    public Utterance() {}
+
+    /**
+     * Returns the plain text of a given Markup if it was set; if it's not set, recursively call the
+     * this same method on its children.
+     */
+    private String constructPlainText(Markup m) {
+        StringBuilder plainText = new StringBuilder();
+        if (m.getPlainText() != null) {
+            plainText.append(m.getPlainText());
+        } else {
+            for (Markup nestedMarkup : m.getNestedMarkups()) {
+                String nestedPlainText = constructPlainText(nestedMarkup);
+                if (!nestedPlainText.isEmpty()) {
+                    if (plainText.length() != 0) {
+                        plainText.append(" ");
+                    }
+                    plainText.append(nestedPlainText);
+                }
+            }
+        }
+        return plainText.toString();
+    }
+
+    /**
+     * Creates a Markup instance with auto generated plain texts for the relevant nodes, in case the
+     * user has not provided one already.
+     * @return A Markup instance representing this utterance.
+     */
+    public Markup createMarkup() {
+        Markup markup = new Markup(TYPE_UTTERANCE);
+        StringBuilder plainText = new StringBuilder();
+        for (AbstractTts<? extends AbstractTts<?>> say : says) {
+            // Get a copy of this markup, and generate a plaintext for it if is not set.
+            Markup sayMarkup = say.getMarkup();
+            if (sayMarkup.getPlainText() == null) {
+                sayMarkup.setPlainText(say.generatePlainText());
+            }
+            if (plainText.length() != 0) {
+                plainText.append(" ");
+            }
+            plainText.append(constructPlainText(sayMarkup));
+            markup.addNestedMarkup(sayMarkup);
+        }
+        if (mNoWarningOnFallback != null) {
+            markup.setParameter(KEY_NO_WARNING_ON_FALLBACK,
+                                mNoWarningOnFallback ? "true" : "false");
+        }
+        markup.setPlainText(plainText.toString());
+        return markup;
+    }
+
+    /**
+     * Appends an element to this Utterance instance.
+     * @return this instance
+     */
+    public Utterance append(AbstractTts<? extends AbstractTts<?>> say) {
+        says.add(say);
+        return this;
+    }
+
+    private Utterance append(Markup markup) {
+        if (markup.getType().equals(TtsText.TYPE_TEXT)) {
+            append(new TtsText(markup));
+        } else if (markup.getType().equals(TtsCardinal.TYPE_CARDINAL)) {
+            append(new TtsCardinal(markup));
+        } else {
+            // Unknown node, a class we don't know about.
+            if (markup.getPlainText() != null) {
+                append(new TtsText(markup.getPlainText()));
+            } else {
+                // No plainText specified; add its children
+                // seperately. In case of a new prosody node,
+                // we would still verbalize it correctly.
+                for (Markup nested : markup.getNestedMarkups()) {
+                    append(nested);
+                }
+            }
+        }
+        return this;
+    }
+
+    /**
+     * Returns a string representation of this Utterance instance. Can be deserialized back to an
+     * Utterance instance with utteranceFromString(). Can be used to store utterances to be used
+     * at a later time.
+     */
+    public String toString() {
+        String out = "type: \"" + TYPE_UTTERANCE + "\"";
+        if (mNoWarningOnFallback != null) {
+            out += " no_warning_on_fallback: \"" + (mNoWarningOnFallback ? "true" : "false") + "\"";
+        }
+        for (AbstractTts<? extends AbstractTts<?>> say : says) {
+            out += " markup { " + say.getMarkup().toString() + " }";
+        }
+        return out;
+    }
+
+    /**
+     * Returns an Utterance instance from the string representation generated by toString().
+     * @param string The string representation generated by toString().
+     * @return The new Utterance instance.
+     * @throws {@link IllegalArgumentException} if the input cannot be correctly parsed.
+     */
+    static public Utterance utteranceFromString(String string) throws IllegalArgumentException {
+        Utterance utterance = new Utterance();
+        Markup markup = Markup.markupFromString(string);
+        if (!markup.getType().equals(TYPE_UTTERANCE)) {
+            throw new IllegalArgumentException("Top level markup should be of type \"" +
+                                               TYPE_UTTERANCE + "\", but was of type \"" +
+                                               markup.getType() + "\".") ;
+        }
+        for (Markup nestedMarkup : markup.getNestedMarkups()) {
+            utterance.append(nestedMarkup);
+        }
+        return utterance;
+    }
+
+    /**
+     * Appends a new TtsText with the given text.
+     * @param text The text to synthesize.
+     * @return This instance.
+     */
+    public Utterance append(String text) {
+        return append(new TtsText(text));
+    }
+
+    /**
+     * Appends a TtsCardinal representing the given number.
+     * @param integer The integer to synthesize.
+     * @return this
+     */
+    public Utterance append(int integer) {
+        return append(new TtsCardinal(integer));
+    }
+
+    /**
+     * Returns the n'th element in this Utterance.
+     * @param i The index.
+     * @return The n'th element in this Utterance.
+     * @throws {@link IndexOutOfBoundsException} - if i < 0 || i >= size()
+     */
+    public AbstractTts<? extends AbstractTts<?>> get(int i) {
+        return says.get(i);
+    }
+
+    /**
+     * Returns the number of elements in this Utterance.
+     * @return The number of elements in this Utterance.
+     */
+    public int size() {
+        return says.size();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if ( this == o ) return true;
+        if ( !(o instanceof Utterance) ) return false;
+        Utterance utt = (Utterance) o;
+
+        if (says.size() != utt.says.size()) {
+            return false;
+        }
+
+        for (int i = 0; i < says.size(); i++) {
+            if (!says.get(i).getMarkup().equals(utt.says.get(i).getMarkup())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Can be set to true or false, true indicating that the user provided only a string to the API,
+     * at which the system will not issue a warning if the synthesizer falls back onto the plain
+     * text when the synthesizer does not support Markup.
+     */
+    public Utterance setNoWarningOnFallback(boolean noWarning) {
+        mNoWarningOnFallback = noWarning;
+        return this;
+    }
+}
diff --git a/core/java/android/speech/tts/VoiceInfo.java b/core/java/android/speech/tts/VoiceInfo.java
index 57cb615..71629dc 100644
--- a/core/java/android/speech/tts/VoiceInfo.java
+++ b/core/java/android/speech/tts/VoiceInfo.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
 package android.speech.tts;
 
 import android.os.Bundle;
@@ -16,7 +31,6 @@
  * callback. The name can be used to reference a VoiceInfo in an instance of {@link RequestConfig};
  * the {@link TextToSpeechClient.Params#FALLBACK_VOICE_NAME} voice parameter is an example of this.
  * It is recommended that the voice name never change during the TTS service lifetime.
- * @hide
  */
 public final class VoiceInfo implements Parcelable {
     /** Very low, but still intelligible quality of speech synthesis */
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index f06ae71..48122d6 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -48,10 +48,11 @@
 import android.text.style.UnderlineSpan;
 import android.util.Log;
 import android.util.Printer;
-
 import android.view.View;
+
 import com.android.internal.R;
 import com.android.internal.util.ArrayUtils;
+
 import libcore.icu.ICU;
 
 import java.lang.reflect.Array;
@@ -229,7 +230,12 @@
     public static boolean regionMatches(CharSequence one, int toffset,
                                         CharSequence two, int ooffset,
                                         int len) {
-        char[] temp = obtain(2 * len);
+        int tempLen = 2 * len;
+        if (tempLen < len) {
+            // Integer overflow; len is unreasonably large
+            throw new IndexOutOfBoundsException();
+        }
+        char[] temp = obtain(tempLen);
 
         getChars(one, toffset, toffset + len, temp, 0);
         getChars(two, ooffset, ooffset + len, temp, len);
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index deb138d..c1341e1 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -465,32 +465,39 @@
         String address;
         int base = 0;
 
-        while ((address = WebView.findAddress(string)) != null) {
-            int start = string.indexOf(address);
+        try {
+            while ((address = WebView.findAddress(string)) != null) {
+                int start = string.indexOf(address);
 
-            if (start < 0) {
-                break;
+                if (start < 0) {
+                    break;
+                }
+
+                LinkSpec spec = new LinkSpec();
+                int length = address.length();
+                int end = start + length;
+
+                spec.start = base + start;
+                spec.end = base + end;
+                string = string.substring(end);
+                base += end;
+
+                String encodedAddress = null;
+
+                try {
+                    encodedAddress = URLEncoder.encode(address,"UTF-8");
+                } catch (UnsupportedEncodingException e) {
+                    continue;
+                }
+
+                spec.url = "geo:0,0?q=" + encodedAddress;
+                links.add(spec);
             }
-
-            LinkSpec spec = new LinkSpec();
-            int length = address.length();
-            int end = start + length;
-            
-            spec.start = base + start;
-            spec.end = base + end;
-            string = string.substring(end);
-            base += end;
-
-            String encodedAddress = null;
-
-            try {
-                encodedAddress = URLEncoder.encode(address,"UTF-8");
-            } catch (UnsupportedEncodingException e) {
-                continue;
-            }
-
-            spec.url = "geo:0,0?q=" + encodedAddress;
-            links.add(spec);
+        } catch (UnsupportedOperationException e) {
+            // findAddress may fail with an unsupported exception on platforms without a WebView.
+            // In this case, we will not append anything to the links variable: it would have died
+            // in WebView.findAddress.
+            return;
         }
     }
 
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index e9c2bba..0a4f641 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -603,76 +603,76 @@
         for (int i = 0; i < startValuesList.size(); ++i) {
             TransitionValues start = startValuesList.get(i);
             TransitionValues end = endValuesList.get(i);
-            // Only bother trying to animate with values that differ between start/end
-            if (start != null || end != null) {
-                if (start == null || !start.equals(end)) {
-                    if (DBG) {
-                        View view = (end != null) ? end.view : start.view;
-                        Log.d(LOG_TAG, "  differing start/end values for view " +
-                                view);
-                        if (start == null || end == null) {
-                            Log.d(LOG_TAG, "    " + ((start == null) ?
-                                    "start null, end non-null" : "start non-null, end null"));
-                        } else {
-                            for (String key : start.values.keySet()) {
-                                Object startValue = start.values.get(key);
-                                Object endValue = end.values.get(key);
-                                if (startValue != endValue && !startValue.equals(endValue)) {
-                                    Log.d(LOG_TAG, "    " + key + ": start(" + startValue +
-                                            "), end(" + endValue +")");
-                                }
+            // Only bother trying to animate with valid values that differ between start/end
+            boolean isInvalidStart = start != null && !isValidTarget(start.view);
+            boolean isInvalidEnd = end != null && !isValidTarget(end.view);
+            boolean isChanged = start != end && (start == null || !start.equals(end));
+            if (isChanged && !isInvalidStart && !isInvalidEnd) {
+                if (DBG) {
+                    View view = (end != null) ? end.view : start.view;
+                    Log.d(LOG_TAG, "  differing start/end values for view " + view);
+                    if (start == null || end == null) {
+                        Log.d(LOG_TAG, "    " + ((start == null) ?
+                                "start null, end non-null" : "start non-null, end null"));
+                    } else {
+                        for (String key : start.values.keySet()) {
+                            Object startValue = start.values.get(key);
+                            Object endValue = end.values.get(key);
+                            if (startValue != endValue && !startValue.equals(endValue)) {
+                                Log.d(LOG_TAG, "    " + key + ": start(" + startValue +
+                                        "), end(" + endValue + ")");
                             }
                         }
                     }
-                    // TODO: what to do about targetIds and itemIds?
-                    Animator animator = createAnimator(sceneRoot, start, end);
+                }
+                // TODO: what to do about targetIds and itemIds?
+                Animator animator = createAnimator(sceneRoot, start, end);
+                if (animator != null) {
+                    // Save animation info for future cancellation purposes
+                    View view = null;
+                    TransitionValues infoValues = null;
+                    if (end != null) {
+                        view = end.view;
+                        String[] properties = getTransitionProperties();
+                        if (view != null && properties != null && properties.length > 0) {
+                            infoValues = new TransitionValues();
+                            infoValues.view = view;
+                            TransitionValues newValues = endValues.viewValues.get(view);
+                            if (newValues != null) {
+                                for (int j = 0; j < properties.length; ++j) {
+                                    infoValues.values.put(properties[j],
+                                            newValues.values.get(properties[j]));
+                                }
+                            }
+                            int numExistingAnims = runningAnimators.size();
+                            for (int j = 0; j < numExistingAnims; ++j) {
+                                Animator anim = runningAnimators.keyAt(j);
+                                AnimationInfo info = runningAnimators.get(anim);
+                                if (info.values != null && info.view == view &&
+                                        ((info.name == null && getName() == null) ||
+                                                info.name.equals(getName()))) {
+                                    if (info.values.equals(infoValues)) {
+                                        // Favor the old animator
+                                        animator = null;
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                    } else {
+                        view = (start != null) ? start.view : null;
+                    }
                     if (animator != null) {
-                        // Save animation info for future cancellation purposes
-                        View view = null;
-                        TransitionValues infoValues = null;
-                        if (end != null) {
-                            view = end.view;
-                            String[] properties = getTransitionProperties();
-                            if (view != null && properties != null && properties.length > 0) {
-                                infoValues = new TransitionValues();
-                                infoValues.view = view;
-                                TransitionValues newValues = endValues.viewValues.get(view);
-                                if (newValues != null) {
-                                    for (int j = 0; j < properties.length; ++j) {
-                                        infoValues.values.put(properties[j],
-                                                newValues.values.get(properties[j]));
-                                    }
-                                }
-                                int numExistingAnims = runningAnimators.size();
-                                for (int j = 0; j < numExistingAnims; ++j) {
-                                    Animator anim = runningAnimators.keyAt(j);
-                                    AnimationInfo info = runningAnimators.get(anim);
-                                    if (info.values != null && info.view == view &&
-                                            ((info.name == null && getName() == null) ||
-                                            info.name.equals(getName()))) {
-                                        if (info.values.equals(infoValues)) {
-                                            // Favor the old animator
-                                            animator = null;
-                                            break;
-                                        }
-                                    }
-                                }
-                            }
-                        } else {
-                            view = (start != null) ? start.view : null;
+                        if (mPropagation != null) {
+                            long delay = mPropagation
+                                    .getStartDelay(sceneRoot, this, start, end);
+                            startDelays.put(mAnimators.size(), delay);
+                            minStartDelay = Math.min(delay, minStartDelay);
                         }
-                        if (animator != null) {
-                            if (mPropagation != null) {
-                                long delay = mPropagation
-                                        .getStartDelay(sceneRoot, this, start, end);
-                                startDelays.put(mAnimators.size(), delay);
-                                minStartDelay = Math.min(delay, minStartDelay);
-                            }
-                            AnimationInfo info = new AnimationInfo(view, getName(),
-                                    sceneRoot.getWindowId(), infoValues);
-                            runningAnimators.put(animator, info);
-                            mAnimators.add(animator);
-                        }
+                        AnimationInfo info = new AnimationInfo(view, getName(),
+                                sceneRoot.getWindowId(), infoValues);
+                        runningAnimators.put(animator, info);
+                        mAnimators.add(animator);
                     }
                 }
             }
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 424d860..5056097 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -75,22 +75,10 @@
     // Constructors
     ///////////////////////////////////////////////////////////////////////////
 
-    /**
-     * Creates a canvas to render directly on screen.
-     */
-    GLES20Canvas(boolean translucent) {
-        this(false, translucent);
-    }
-    
-    protected GLES20Canvas(boolean record, boolean translucent) {
-        mOpaque = !translucent;
-
-        if (record) {
-            mRenderer = nCreateDisplayListRenderer();
-        } else {
-            mRenderer = nCreateRenderer();
-        }
-
+    // TODO: Merge with GLES20RecordingCanvas
+    protected GLES20Canvas() {
+        mOpaque = false;
+        mRenderer = nCreateDisplayListRenderer();
         setupFinalizer();
     }
 
@@ -102,7 +90,6 @@
         }
     }
 
-    private static native long nCreateRenderer();
     private static native long nCreateDisplayListRenderer();
     private static native void nResetDisplayListRenderer(long renderer);
     private static native void nDestroyRenderer(long renderer);
@@ -131,36 +118,6 @@
     private static native void nSetProperty(String name, String value);
 
     ///////////////////////////////////////////////////////////////////////////
-    // Hardware layers
-    ///////////////////////////////////////////////////////////////////////////
-
-    @Override
-    void pushLayerUpdate(HardwareLayer layer) {
-        nPushLayerUpdate(mRenderer, layer.getLayer());
-    }
-
-    @Override
-    void cancelLayerUpdate(HardwareLayer layer) {
-        nCancelLayerUpdate(mRenderer, layer.getLayer());
-    }
-
-    @Override
-    void flushLayerUpdates() {
-        nFlushLayerUpdates(mRenderer);
-    }
-
-    @Override
-    void clearLayerUpdates() {
-        nClearLayerUpdates(mRenderer);
-    }
-
-    static native boolean nCopyLayer(long layerId, long bitmap);
-    private static native void nClearLayerUpdates(long renderer);
-    private static native void nFlushLayerUpdates(long renderer);
-    private static native void nPushLayerUpdate(long renderer, long layer);
-    private static native void nCancelLayerUpdate(long renderer, long layer);
-
-    ///////////////////////////////////////////////////////////////////////////
     // Canvas management
     ///////////////////////////////////////////////////////////////////////////
 
@@ -234,20 +191,6 @@
 
     private static native void nFinish(long renderer);
 
-    /**
-     * Returns the size of the stencil buffer required by the underlying
-     * implementation.
-     * 
-     * @return The minimum number of bits the stencil buffer must. Always >= 0.
-     * 
-     * @hide
-     */
-    public static int getStencilSize() {
-        return nGetStencilSize();
-    }
-
-    private static native int nGetStencilSize();
-
     ///////////////////////////////////////////////////////////////////////////
     // Functor
     ///////////////////////////////////////////////////////////////////////////
@@ -284,49 +227,6 @@
      */
     static final int FLUSH_CACHES_FULL = 2;
 
-    /**
-     * Flush caches to reclaim as much memory as possible. The amount of memory
-     * to reclaim is indicate by the level parameter.
-     * 
-     * The level can be one of {@link #FLUSH_CACHES_MODERATE} or
-     * {@link #FLUSH_CACHES_FULL}.
-     * 
-     * @param level Hint about the amount of memory to reclaim
-     */
-    static void flushCaches(int level) {
-        nFlushCaches(level);
-    }
-
-    private static native void nFlushCaches(int level);
-
-    /**
-     * Release all resources associated with the underlying caches. This should
-     * only be called after a full flushCaches().
-     * 
-     * @hide
-     */
-    static void terminateCaches() {
-        nTerminateCaches();
-    }
-
-    private static native void nTerminateCaches();
-
-    static boolean initCaches() {
-        return nInitCaches();
-    }
-
-    private static native boolean nInitCaches();
-
-    ///////////////////////////////////////////////////////////////////////////
-    // Atlas
-    ///////////////////////////////////////////////////////////////////////////
-
-    static void initAtlas(GraphicBuffer buffer, long[] map) {
-        nInitAtlas(buffer, map, map.length);
-    }
-
-    private static native void nInitAtlas(GraphicBuffer buffer, long[] map, int count);
-
     ///////////////////////////////////////////////////////////////////////////
     // Display list
     ///////////////////////////////////////////////////////////////////////////
@@ -899,12 +799,6 @@
     private static native void nDrawPath(long renderer, long path, long paint);
     private static native void nDrawRects(long renderer, long region, long paint);
 
-    void drawRects(float[] rects, int count, Paint paint) {
-        nDrawRects(mRenderer, rects, count, paint.mNativePaint);
-    }
-
-    private static native void nDrawRects(long renderer, float[] rects, int count, long paint);
-
     @Override
     public void drawPicture(Picture picture) {
         if (picture.createdFromStream) {
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
index a94ec3a..b2961e5 100644
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ b/core/java/android/view/GLES20RecordingCanvas.java
@@ -36,7 +36,7 @@
     RenderNode mNode;
 
     private GLES20RecordingCanvas() {
-        super(true, true);
+        super();
     }
 
     static GLES20RecordingCanvas obtain(@NonNull RenderNode node) {
diff --git a/core/java/android/view/GLRenderer.java b/core/java/android/view/GLRenderer.java
deleted file mode 100644
index f1163e2..0000000
--- a/core/java/android/view/GLRenderer.java
+++ /dev/null
@@ -1,1521 +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.view;
-
-import static javax.microedition.khronos.egl.EGL10.EGL_ALPHA_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_BAD_NATIVE_WINDOW;
-import static javax.microedition.khronos.egl.EGL10.EGL_BLUE_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_CONFIG_CAVEAT;
-import static javax.microedition.khronos.egl.EGL10.EGL_DEFAULT_DISPLAY;
-import static javax.microedition.khronos.egl.EGL10.EGL_DEPTH_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_DRAW;
-import static javax.microedition.khronos.egl.EGL10.EGL_GREEN_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_HEIGHT;
-import static javax.microedition.khronos.egl.EGL10.EGL_NONE;
-import static javax.microedition.khronos.egl.EGL10.EGL_NO_CONTEXT;
-import static javax.microedition.khronos.egl.EGL10.EGL_NO_DISPLAY;
-import static javax.microedition.khronos.egl.EGL10.EGL_NO_SURFACE;
-import static javax.microedition.khronos.egl.EGL10.EGL_RED_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_RENDERABLE_TYPE;
-import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLES;
-import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLE_BUFFERS;
-import static javax.microedition.khronos.egl.EGL10.EGL_STENCIL_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_SUCCESS;
-import static javax.microedition.khronos.egl.EGL10.EGL_SURFACE_TYPE;
-import static javax.microedition.khronos.egl.EGL10.EGL_WIDTH;
-import static javax.microedition.khronos.egl.EGL10.EGL_WINDOW_BIT;
-
-import android.content.ComponentCallbacks2;
-import android.graphics.Bitmap;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.SurfaceTexture;
-import android.opengl.EGL14;
-import android.opengl.GLUtils;
-import android.opengl.ManagedEGLContext;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.Trace;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Surface.OutOfResourcesException;
-
-import com.google.android.gles_jni.EGLImpl;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.locks.ReentrantLock;
-
-import javax.microedition.khronos.egl.EGL10;
-import javax.microedition.khronos.egl.EGL11;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.egl.EGLContext;
-import javax.microedition.khronos.egl.EGLDisplay;
-import javax.microedition.khronos.egl.EGLSurface;
-import javax.microedition.khronos.opengles.GL;
-
-/**
- * Hardware renderer using OpenGL
- *
- * @hide
- */
-public class GLRenderer extends HardwareRenderer {
-    static final int SURFACE_STATE_ERROR = 0;
-    static final int SURFACE_STATE_SUCCESS = 1;
-    static final int SURFACE_STATE_UPDATED = 2;
-
-    static final int FUNCTOR_PROCESS_DELAY = 4;
-
-    /**
-     * Number of frames to profile.
-     */
-    private static final int PROFILE_MAX_FRAMES = 128;
-
-    /**
-     * Number of floats per profiled frame.
-     */
-    private static final int PROFILE_FRAME_DATA_COUNT = 3;
-
-    private static final int PROFILE_DRAW_MARGIN = 0;
-    private static final int PROFILE_DRAW_WIDTH = 3;
-    private static final int[] PROFILE_DRAW_COLORS = { 0xcf3e66cc, 0xcfdc3912, 0xcfe69800 };
-    private static final int PROFILE_DRAW_CURRENT_FRAME_COLOR = 0xcf5faa4d;
-    private static final int PROFILE_DRAW_THRESHOLD_COLOR = 0xff5faa4d;
-    private static final int PROFILE_DRAW_THRESHOLD_STROKE_WIDTH = 2;
-    private static final int PROFILE_DRAW_DP_PER_MS = 7;
-
-    private static final String[] VISUALIZERS = {
-            PROFILE_PROPERTY_VISUALIZE_BARS,
-    };
-
-    private static final String[] OVERDRAW = {
-            OVERDRAW_PROPERTY_SHOW,
-    };
-    private static final int GL_VERSION = 2;
-
-    static EGL10 sEgl;
-    static EGLDisplay sEglDisplay;
-    static EGLConfig sEglConfig;
-    static final Object[] sEglLock = new Object[0];
-    int mWidth = -1, mHeight = -1;
-
-    static final ThreadLocal<ManagedEGLContext> sEglContextStorage
-            = new ThreadLocal<ManagedEGLContext>();
-
-    EGLContext mEglContext;
-    Thread mEglThread;
-
-    EGLSurface mEglSurface;
-
-    GL mGl;
-    HardwareCanvas mCanvas;
-
-    String mName;
-
-    long mFrameCount;
-    Paint mDebugPaint;
-
-    static boolean sDirtyRegions;
-    static final boolean sDirtyRegionsRequested;
-    static {
-        String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true");
-        //noinspection PointlessBooleanExpression,ConstantConditions
-        sDirtyRegions = "true".equalsIgnoreCase(dirtyProperty);
-        sDirtyRegionsRequested = sDirtyRegions;
-    }
-
-    boolean mDirtyRegionsEnabled;
-    boolean mUpdateDirtyRegions;
-
-    boolean mProfileEnabled;
-    int mProfileVisualizerType = -1;
-    float[] mProfileData;
-    ReentrantLock mProfileLock;
-    int mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT;
-
-    GraphDataProvider mDebugDataProvider;
-    float[][] mProfileShapes;
-    Paint mProfilePaint;
-
-    boolean mDebugDirtyRegions;
-    int mDebugOverdraw = -1;
-
-    final boolean mTranslucent;
-
-    private boolean mDestroyed;
-
-    private final Rect mRedrawClip = new Rect();
-
-    private final int[] mSurfaceSize = new int[2];
-
-    private long mDrawDelta = Long.MAX_VALUE;
-
-    private GLES20Canvas mGlCanvas;
-
-    private DisplayMetrics mDisplayMetrics;
-
-    private static EGLSurface sPbuffer;
-    private static final Object[] sPbufferLock = new Object[0];
-
-    private List<HardwareLayer> mLayerUpdates = new ArrayList<HardwareLayer>();
-
-    private static class GLRendererEglContext extends ManagedEGLContext {
-        final Handler mHandler = new Handler();
-
-        public GLRendererEglContext(EGLContext context) {
-            super(context);
-        }
-
-        @Override
-        public void onTerminate(final EGLContext eglContext) {
-            // Make sure we do this on the correct thread.
-            if (mHandler.getLooper() != Looper.myLooper()) {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        onTerminate(eglContext);
-                    }
-                });
-                return;
-            }
-
-            synchronized (sEglLock) {
-                if (sEgl == null) return;
-
-                if (EGLImpl.getInitCount(sEglDisplay) == 1) {
-                    usePbufferSurface(eglContext);
-                    GLES20Canvas.terminateCaches();
-
-                    sEgl.eglDestroyContext(sEglDisplay, eglContext);
-                    sEglContextStorage.set(null);
-                    sEglContextStorage.remove();
-
-                    sEgl.eglDestroySurface(sEglDisplay, sPbuffer);
-                    sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
-                            EGL_NO_SURFACE, EGL_NO_CONTEXT);
-
-                    sEgl.eglReleaseThread();
-                    sEgl.eglTerminate(sEglDisplay);
-
-                    sEgl = null;
-                    sEglDisplay = null;
-                    sEglConfig = null;
-                    sPbuffer = null;
-                }
-            }
-        }
-    }
-
-    HardwareCanvas createCanvas() {
-        return mGlCanvas = new GLES20Canvas(mTranslucent);
-    }
-
-    ManagedEGLContext createManagedContext(EGLContext eglContext) {
-        return new GLRendererEglContext(mEglContext);
-    }
-
-    int[] getConfig(boolean dirtyRegions) {
-        //noinspection PointlessBooleanExpression,ConstantConditions
-        final int stencilSize = GLES20Canvas.getStencilSize();
-        final int swapBehavior = dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
-
-        return new int[] {
-                EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
-                EGL_RED_SIZE, 8,
-                EGL_GREEN_SIZE, 8,
-                EGL_BLUE_SIZE, 8,
-                EGL_ALPHA_SIZE, 8,
-                EGL_DEPTH_SIZE, 0,
-                EGL_CONFIG_CAVEAT, EGL_NONE,
-                EGL_STENCIL_SIZE, stencilSize,
-                EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior,
-                EGL_NONE
-        };
-    }
-
-    void initCaches() {
-        if (GLES20Canvas.initCaches()) {
-            // Caches were (re)initialized, rebind atlas
-            initAtlas();
-        }
-    }
-
-    void initAtlas() {
-        IBinder binder = ServiceManager.getService("assetatlas");
-        if (binder == null) return;
-
-        IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder);
-        try {
-            if (atlas.isCompatible(android.os.Process.myPpid())) {
-                GraphicBuffer buffer = atlas.getBuffer();
-                if (buffer != null) {
-                    long[] map = atlas.getMap();
-                    if (map != null) {
-                        GLES20Canvas.initAtlas(buffer, map);
-                    }
-                    // If IAssetAtlas is not the same class as the IBinder
-                    // we are using a remote service and we can safely
-                    // destroy the graphic buffer
-                    if (atlas.getClass() != binder.getClass()) {
-                        buffer.destroy();
-                    }
-                }
-            }
-        } catch (RemoteException e) {
-            Log.w(LOG_TAG, "Could not acquire atlas", e);
-        }
-    }
-
-    boolean canDraw() {
-        return mGl != null && mCanvas != null && mGlCanvas != null;
-    }
-
-    int onPreDraw(Rect dirty) {
-        return mGlCanvas.onPreDraw(dirty);
-    }
-
-    void onPostDraw() {
-        mGlCanvas.onPostDraw();
-    }
-
-    void drawProfileData(View.AttachInfo attachInfo) {
-        if (mDebugDataProvider != null) {
-            final GraphDataProvider provider = mDebugDataProvider;
-            initProfileDrawData(attachInfo, provider);
-
-            final int height = provider.getVerticalUnitSize();
-            final int margin = provider.getHorizontaUnitMargin();
-            final int width = provider.getHorizontalUnitSize();
-
-            int x = 0;
-            int count = 0;
-            int current = 0;
-
-            final float[] data = provider.getData();
-            final int elementCount = provider.getElementCount();
-            final int graphType = provider.getGraphType();
-
-            int totalCount = provider.getFrameCount() * elementCount;
-            if (graphType == GraphDataProvider.GRAPH_TYPE_LINES) {
-                totalCount -= elementCount;
-            }
-
-            for (int i = 0; i < totalCount; i += elementCount) {
-                if (data[i] < 0.0f) break;
-
-                int index = count * 4;
-                if (i == provider.getCurrentFrame() * elementCount) current = index;
-
-                x += margin;
-                int x2 = x + width;
-
-                int y2 = mHeight;
-                int y1 = (int) (y2 - data[i] * height);
-
-                switch (graphType) {
-                    case GraphDataProvider.GRAPH_TYPE_BARS: {
-                        for (int j = 0; j < elementCount; j++) {
-                            //noinspection MismatchedReadAndWriteOfArray
-                            final float[] r = mProfileShapes[j];
-                            r[index] = x;
-                            r[index + 1] = y1;
-                            r[index + 2] = x2;
-                            r[index + 3] = y2;
-
-                            y2 = y1;
-                            if (j < elementCount - 1) {
-                                y1 = (int) (y2 - data[i + j + 1] * height);
-                            }
-                        }
-                    } break;
-                    case GraphDataProvider.GRAPH_TYPE_LINES: {
-                        for (int j = 0; j < elementCount; j++) {
-                            //noinspection MismatchedReadAndWriteOfArray
-                            final float[] r = mProfileShapes[j];
-                            r[index] = (x + x2) * 0.5f;
-                            r[index + 1] = index == 0 ? y1 : r[index - 1];
-                            r[index + 2] = r[index] + width;
-                            r[index + 3] = y1;
-
-                            y2 = y1;
-                            if (j < elementCount - 1) {
-                                y1 = (int) (y2 - data[i + j + 1] * height);
-                            }
-                        }
-                    } break;
-                }
-
-
-                x += width;
-                count++;
-            }
-
-            x += margin;
-
-            drawGraph(graphType, count);
-            drawCurrentFrame(graphType, current);
-            drawThreshold(x, height);
-        }
-    }
-
-    private void drawGraph(int graphType, int count) {
-        for (int i = 0; i < mProfileShapes.length; i++) {
-            mDebugDataProvider.setupGraphPaint(mProfilePaint, i);
-            switch (graphType) {
-                case GraphDataProvider.GRAPH_TYPE_BARS:
-                    mGlCanvas.drawRects(mProfileShapes[i], count * 4, mProfilePaint);
-                    break;
-                case GraphDataProvider.GRAPH_TYPE_LINES:
-                    mGlCanvas.drawLines(mProfileShapes[i], 0, count * 4, mProfilePaint);
-                    break;
-            }
-        }
-    }
-
-    private void drawCurrentFrame(int graphType, int index) {
-        if (index >= 0) {
-            mDebugDataProvider.setupCurrentFramePaint(mProfilePaint);
-            switch (graphType) {
-                case GraphDataProvider.GRAPH_TYPE_BARS:
-                    mGlCanvas.drawRect(mProfileShapes[2][index], mProfileShapes[2][index + 1],
-                            mProfileShapes[2][index + 2], mProfileShapes[0][index + 3],
-                            mProfilePaint);
-                    break;
-                case GraphDataProvider.GRAPH_TYPE_LINES:
-                    mGlCanvas.drawLine(mProfileShapes[2][index], mProfileShapes[2][index + 1],
-                            mProfileShapes[2][index], mHeight, mProfilePaint);
-                    break;
-            }
-        }
-    }
-
-    private void drawThreshold(int x, int height) {
-        float threshold = mDebugDataProvider.getThreshold();
-        if (threshold > 0.0f) {
-            mDebugDataProvider.setupThresholdPaint(mProfilePaint);
-            int y = (int) (mHeight - threshold * height);
-            mGlCanvas.drawLine(0.0f, y, x, y, mProfilePaint);
-        }
-    }
-
-    private void initProfileDrawData(View.AttachInfo attachInfo, GraphDataProvider provider) {
-        if (mProfileShapes == null) {
-            final int elementCount = provider.getElementCount();
-            final int frameCount = provider.getFrameCount();
-
-            mProfileShapes = new float[elementCount][];
-            for (int i = 0; i < elementCount; i++) {
-                mProfileShapes[i] = new float[frameCount * 4];
-            }
-
-            mProfilePaint = new Paint();
-        }
-
-        mProfilePaint.reset();
-        if (provider.getGraphType() == GraphDataProvider.GRAPH_TYPE_LINES) {
-            mProfilePaint.setAntiAlias(true);
-        }
-
-        if (mDisplayMetrics == null) {
-            mDisplayMetrics = new DisplayMetrics();
-        }
-
-        attachInfo.mDisplay.getMetrics(mDisplayMetrics);
-        provider.prepare(mDisplayMetrics);
-    }
-
-    @Override
-    void destroy(boolean full) {
-        try {
-            if (full && mCanvas != null) {
-                mCanvas = null;
-            }
-
-            if (!isEnabled() || mDestroyed) {
-                setEnabled(false);
-                return;
-            }
-
-            destroySurface();
-            setEnabled(false);
-
-            mDestroyed = true;
-            mGl = null;
-        } finally {
-            if (full && mGlCanvas != null) {
-                mGlCanvas = null;
-            }
-        }
-    }
-
-    @Override
-    void pushLayerUpdate(HardwareLayer layer) {
-        mLayerUpdates.add(layer);
-    }
-
-    @Override
-    void flushLayerUpdates() {
-        if (validate()) {
-            flushLayerChanges();
-            mGlCanvas.flushLayerUpdates();
-        }
-    }
-
-    @Override
-    HardwareLayer createTextureLayer() {
-        validate();
-        return HardwareLayer.createTextureLayer(this);
-    }
-
-    @Override
-    public HardwareLayer createDisplayListLayer(int width, int height) {
-        validate();
-        return HardwareLayer.createDisplayListLayer(this, width, height);
-    }
-
-    boolean hasContext() {
-        return sEgl != null && mEglContext != null
-                && mEglContext.equals(sEgl.eglGetCurrentContext());
-    }
-
-    @Override
-    void onLayerDestroyed(HardwareLayer layer) {
-        if (mGlCanvas != null) {
-            mGlCanvas.cancelLayerUpdate(layer);
-        }
-        mLayerUpdates.remove(layer);
-    }
-
-    @Override
-    public SurfaceTexture createSurfaceTexture(HardwareLayer layer) {
-        return layer.createSurfaceTexture();
-    }
-
-    @Override
-    boolean copyLayerInto(HardwareLayer layer, Bitmap bitmap) {
-        if (!validate()) {
-            throw new IllegalStateException("Could not acquire hardware rendering context");
-        }
-        layer.flushChanges();
-        return GLES20Canvas.nCopyLayer(layer.getLayer(), bitmap.mNativeBitmap);
-    }
-
-    @Override
-    boolean safelyRun(Runnable action) {
-        boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR;
-
-        if (needsContext) {
-            GLRendererEglContext managedContext =
-                    (GLRendererEglContext) sEglContextStorage.get();
-            if (managedContext == null) return false;
-            usePbufferSurface(managedContext.getContext());
-        }
-
-        try {
-            action.run();
-        } finally {
-            if (needsContext) {
-                sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
-                        EGL_NO_SURFACE, EGL_NO_CONTEXT);
-            }
-        }
-
-        return true;
-    }
-
-    @Override
-    void invokeFunctor(long functor, boolean waitForCompletion) {
-        boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR;
-        boolean hasContext = !needsContext;
-
-        if (needsContext) {
-            GLRendererEglContext managedContext =
-                    (GLRendererEglContext) sEglContextStorage.get();
-            if (managedContext != null) {
-                usePbufferSurface(managedContext.getContext());
-                hasContext = true;
-            }
-        }
-
-        try {
-            nInvokeFunctor(functor, hasContext);
-        } finally {
-            if (needsContext) {
-                sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
-                        EGL_NO_SURFACE, EGL_NO_CONTEXT);
-            }
-        }
-    }
-
-    private static native void nInvokeFunctor(long functor, boolean hasContext);
-
-    @Override
-    void destroyHardwareResources(final View view) {
-        if (view != null) {
-            safelyRun(new Runnable() {
-                @Override
-                public void run() {
-                    if (mCanvas != null) {
-                        mCanvas.clearLayerUpdates();
-                    }
-                    destroyResources(view);
-                    GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
-                }
-            });
-        }
-    }
-
-    private static void destroyResources(View view) {
-        view.destroyHardwareResources();
-
-        if (view instanceof ViewGroup) {
-            ViewGroup group = (ViewGroup) view;
-
-            int count = group.getChildCount();
-            for (int i = 0; i < count; i++) {
-                destroyResources(group.getChildAt(i));
-            }
-        }
-    }
-
-    static void startTrimMemory(int level) {
-        if (sEgl == null || sEglConfig == null) return;
-
-        GLRendererEglContext managedContext =
-                (GLRendererEglContext) sEglContextStorage.get();
-        // We do not have OpenGL objects
-        if (managedContext == null) {
-            return;
-        } else {
-            usePbufferSurface(managedContext.getContext());
-        }
-
-        if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
-            GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_FULL);
-        } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
-            GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE);
-        }
-    }
-
-    static void endTrimMemory() {
-        if (sEgl != null && sEglDisplay != null) {
-            sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-        }
-    }
-
-    private static void usePbufferSurface(EGLContext eglContext) {
-        synchronized (sPbufferLock) {
-            // Create a temporary 1x1 pbuffer so we have a context
-            // to clear our OpenGL objects
-            if (sPbuffer == null) {
-                sPbuffer = sEgl.eglCreatePbufferSurface(sEglDisplay, sEglConfig, new int[] {
-                        EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE
-                });
-            }
-        }
-        sEgl.eglMakeCurrent(sEglDisplay, sPbuffer, sPbuffer, eglContext);
-    }
-
-    GLRenderer(boolean translucent) {
-        mTranslucent = translucent;
-
-        loadSystemProperties();
-    }
-
-    @Override
-    void setOpaque(boolean opaque) {
-        // Not supported
-    }
-
-    @Override
-    boolean loadSystemProperties() {
-        boolean value;
-        boolean changed = false;
-
-        String profiling = SystemProperties.get(PROFILE_PROPERTY);
-        int graphType = search(VISUALIZERS, profiling);
-        value = graphType >= 0;
-
-        if (graphType != mProfileVisualizerType) {
-            changed = true;
-            mProfileVisualizerType = graphType;
-
-            mProfileShapes = null;
-            mProfilePaint = null;
-
-            if (value) {
-                mDebugDataProvider = new GraphDataProvider(graphType);
-            } else {
-                mDebugDataProvider = null;
-            }
-        }
-
-        // If on-screen profiling is not enabled, we need to check whether
-        // console profiling only is enabled
-        if (!value) {
-            value = Boolean.parseBoolean(profiling);
-        }
-
-        if (value != mProfileEnabled) {
-            changed = true;
-            mProfileEnabled = value;
-
-            if (mProfileEnabled) {
-                Log.d(LOG_TAG, "Profiling hardware renderer");
-
-                int maxProfileFrames = SystemProperties.getInt(PROFILE_MAXFRAMES_PROPERTY,
-                        PROFILE_MAX_FRAMES);
-                mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT];
-                for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) {
-                    mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1;
-                }
-
-                mProfileLock = new ReentrantLock();
-            } else {
-                mProfileData = null;
-                mProfileLock = null;
-                mProfileVisualizerType = -1;
-            }
-
-            mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT;
-        }
-
-        value = SystemProperties.getBoolean(DEBUG_DIRTY_REGIONS_PROPERTY, false);
-        if (value != mDebugDirtyRegions) {
-            changed = true;
-            mDebugDirtyRegions = value;
-
-            if (mDebugDirtyRegions) {
-                Log.d(LOG_TAG, "Debugging dirty regions");
-            }
-        }
-
-        String overdraw = SystemProperties.get(HardwareRenderer.DEBUG_OVERDRAW_PROPERTY);
-        int debugOverdraw = search(OVERDRAW, overdraw);
-        if (debugOverdraw != mDebugOverdraw) {
-            changed = true;
-            mDebugOverdraw = debugOverdraw;
-        }
-
-        if (loadProperties()) {
-            changed = true;
-        }
-
-        return changed;
-    }
-
-    private static int search(String[] values, String value) {
-        for (int i = 0; i < values.length; i++) {
-            if (values[i].equals(value)) return i;
-        }
-        return -1;
-    }
-
-    @Override
-    void dumpGfxInfo(PrintWriter pw, FileDescriptor fd) {
-        if (mProfileEnabled) {
-            pw.printf("\n\tDraw\tProcess\tExecute\n");
-
-            mProfileLock.lock();
-            try {
-                for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) {
-                    if (mProfileData[i] < 0) {
-                        break;
-                    }
-                    pw.printf("\t%3.2f\t%3.2f\t%3.2f\n", mProfileData[i], mProfileData[i + 1],
-                            mProfileData[i + 2]);
-                    mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1;
-                }
-                mProfileCurrentFrame = mProfileData.length;
-            } finally {
-                mProfileLock.unlock();
-            }
-        }
-    }
-
-    /**
-     * Indicates whether this renderer instance can track and update dirty regions.
-     */
-    boolean hasDirtyRegions() {
-        return mDirtyRegionsEnabled;
-    }
-
-    /**
-     * Checks for OpenGL errors. If an error has occured, {@link #destroy(boolean)}
-     * is invoked and the requested flag is turned off. The error code is
-     * also logged as a warning.
-     */
-    void checkEglErrors() {
-        if (isEnabled()) {
-            checkEglErrorsForced();
-        }
-    }
-
-    private void checkEglErrorsForced() {
-        int error = sEgl.eglGetError();
-        if (error != EGL_SUCCESS) {
-            // something bad has happened revert to
-            // normal rendering.
-            Log.w(LOG_TAG, "EGL error: " + GLUtils.getEGLErrorString(error));
-            fallback(error != EGL11.EGL_CONTEXT_LOST);
-        }
-    }
-
-    private void fallback(boolean fallback) {
-        destroy(true);
-        if (fallback) {
-            // we'll try again if it was context lost
-            setRequested(false);
-            Log.w(LOG_TAG, "Mountain View, we've had a problem here. "
-                    + "Switching back to software rendering.");
-        }
-    }
-
-    @Override
-    boolean initialize(Surface surface) throws OutOfResourcesException {
-        if (isRequested() && !isEnabled()) {
-            boolean contextCreated = initializeEgl();
-            mGl = createEglSurface(surface);
-            mDestroyed = false;
-
-            if (mGl != null) {
-                int err = sEgl.eglGetError();
-                if (err != EGL_SUCCESS) {
-                    destroy(true);
-                    setRequested(false);
-                } else {
-                    if (mCanvas == null) {
-                        mCanvas = createCanvas();
-                    }
-                    setEnabled(true);
-
-                    if (contextCreated) {
-                        initAtlas();
-                    }
-                }
-
-                return mCanvas != null;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    void updateSurface(Surface surface) throws OutOfResourcesException {
-        if (isRequested() && isEnabled()) {
-            createEglSurface(surface);
-        }
-    }
-
-    @Override
-    void pauseSurface(Surface surface) {
-        // No-op
-    }
-
-    boolean initializeEgl() {
-        synchronized (sEglLock) {
-            if (sEgl == null && sEglConfig == null) {
-                sEgl = (EGL10) EGLContext.getEGL();
-
-                // Get to the default display.
-                sEglDisplay = sEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
-
-                if (sEglDisplay == EGL_NO_DISPLAY) {
-                    throw new RuntimeException("eglGetDisplay failed "
-                            + GLUtils.getEGLErrorString(sEgl.eglGetError()));
-                }
-
-                // We can now initialize EGL for that display
-                int[] version = new int[2];
-                if (!sEgl.eglInitialize(sEglDisplay, version)) {
-                    throw new RuntimeException("eglInitialize failed " +
-                            GLUtils.getEGLErrorString(sEgl.eglGetError()));
-                }
-
-                checkEglErrorsForced();
-
-                sEglConfig = loadEglConfig();
-            }
-        }
-
-        ManagedEGLContext managedContext = sEglContextStorage.get();
-        mEglContext = managedContext != null ? managedContext.getContext() : null;
-        mEglThread = Thread.currentThread();
-
-        if (mEglContext == null) {
-            mEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
-            sEglContextStorage.set(createManagedContext(mEglContext));
-            return true;
-        }
-
-        return false;
-    }
-
-    private EGLConfig loadEglConfig() {
-        EGLConfig eglConfig = chooseEglConfig();
-        if (eglConfig == null) {
-            // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without
-            if (sDirtyRegions) {
-                sDirtyRegions = false;
-                eglConfig = chooseEglConfig();
-                if (eglConfig == null) {
-                    throw new RuntimeException("eglConfig not initialized");
-                }
-            } else {
-                throw new RuntimeException("eglConfig not initialized");
-            }
-        }
-        return eglConfig;
-    }
-
-    private EGLConfig chooseEglConfig() {
-        EGLConfig[] configs = new EGLConfig[1];
-        int[] configsCount = new int[1];
-        int[] configSpec = getConfig(sDirtyRegions);
-
-        // Debug
-        final String debug = SystemProperties.get(PRINT_CONFIG_PROPERTY, "");
-        if ("all".equalsIgnoreCase(debug)) {
-            sEgl.eglChooseConfig(sEglDisplay, configSpec, null, 0, configsCount);
-
-            EGLConfig[] debugConfigs = new EGLConfig[configsCount[0]];
-            sEgl.eglChooseConfig(sEglDisplay, configSpec, debugConfigs,
-                    configsCount[0], configsCount);
-
-            for (EGLConfig config : debugConfigs) {
-                printConfig(config);
-            }
-        }
-
-        if (!sEgl.eglChooseConfig(sEglDisplay, configSpec, configs, 1, configsCount)) {
-            throw new IllegalArgumentException("eglChooseConfig failed " +
-                    GLUtils.getEGLErrorString(sEgl.eglGetError()));
-        } else if (configsCount[0] > 0) {
-            if ("choice".equalsIgnoreCase(debug)) {
-                printConfig(configs[0]);
-            }
-            return configs[0];
-        }
-
-        return null;
-    }
-
-    private static void printConfig(EGLConfig config) {
-        int[] value = new int[1];
-
-        Log.d(LOG_TAG, "EGL configuration " + config + ":");
-
-        sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_RED_SIZE, value);
-        Log.d(LOG_TAG, "  RED_SIZE = " + value[0]);
-
-        sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_GREEN_SIZE, value);
-        Log.d(LOG_TAG, "  GREEN_SIZE = " + value[0]);
-
-        sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_BLUE_SIZE, value);
-        Log.d(LOG_TAG, "  BLUE_SIZE = " + value[0]);
-
-        sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_ALPHA_SIZE, value);
-        Log.d(LOG_TAG, "  ALPHA_SIZE = " + value[0]);
-
-        sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_DEPTH_SIZE, value);
-        Log.d(LOG_TAG, "  DEPTH_SIZE = " + value[0]);
-
-        sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_STENCIL_SIZE, value);
-        Log.d(LOG_TAG, "  STENCIL_SIZE = " + value[0]);
-
-        sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLE_BUFFERS, value);
-        Log.d(LOG_TAG, "  SAMPLE_BUFFERS = " + value[0]);
-
-        sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLES, value);
-        Log.d(LOG_TAG, "  SAMPLES = " + value[0]);
-
-        sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SURFACE_TYPE, value);
-        Log.d(LOG_TAG, "  SURFACE_TYPE = 0x" + Integer.toHexString(value[0]));
-
-        sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_CONFIG_CAVEAT, value);
-        Log.d(LOG_TAG, "  CONFIG_CAVEAT = 0x" + Integer.toHexString(value[0]));
-    }
-
-    GL createEglSurface(Surface surface) throws OutOfResourcesException {
-        // Check preconditions.
-        if (sEgl == null) {
-            throw new RuntimeException("egl not initialized");
-        }
-        if (sEglDisplay == null) {
-            throw new RuntimeException("eglDisplay not initialized");
-        }
-        if (sEglConfig == null) {
-            throw new RuntimeException("eglConfig not initialized");
-        }
-        if (Thread.currentThread() != mEglThread) {
-            throw new IllegalStateException("HardwareRenderer cannot be used "
-                    + "from multiple threads");
-        }
-
-        // In case we need to destroy an existing surface
-        destroySurface();
-
-        // Create an EGL surface we can render into.
-        if (!createSurface(surface)) {
-            return null;
-        }
-
-        initCaches();
-
-        return mEglContext.getGL();
-    }
-
-    private void enableDirtyRegions() {
-        // If mDirtyRegions is set, this means we have an EGL configuration
-        // with EGL_SWAP_BEHAVIOR_PRESERVED_BIT set
-        if (sDirtyRegions) {
-            if (!(mDirtyRegionsEnabled = preserveBackBuffer())) {
-                Log.w(LOG_TAG, "Backbuffer cannot be preserved");
-            }
-        } else if (sDirtyRegionsRequested) {
-            // If mDirtyRegions is not set, our EGL configuration does not
-            // have EGL_SWAP_BEHAVIOR_PRESERVED_BIT; however, the default
-            // swap behavior might be EGL_BUFFER_PRESERVED, which means we
-            // want to set mDirtyRegions. We try to do this only if dirty
-            // regions were initially requested as part of the device
-            // configuration (see RENDER_DIRTY_REGIONS)
-            mDirtyRegionsEnabled = isBackBufferPreserved();
-        }
-    }
-
-    EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
-        final int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, GL_VERSION, EGL_NONE };
-
-        EGLContext context = egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT,
-                attribs);
-        if (context == null || context == EGL_NO_CONTEXT) {
-            //noinspection ConstantConditions
-            throw new IllegalStateException(
-                    "Could not create an EGL context. eglCreateContext failed with error: " +
-                    GLUtils.getEGLErrorString(sEgl.eglGetError()));
-        }
-
-        return context;
-    }
-
-    void destroySurface() {
-        if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) {
-            if (mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW))) {
-                sEgl.eglMakeCurrent(sEglDisplay,
-                        EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-            }
-            sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
-            mEglSurface = null;
-        }
-    }
-
-    @Override
-    void invalidate(Surface surface) {
-        // Cancels any existing buffer to ensure we'll get a buffer
-        // of the right size before we call eglSwapBuffers
-        sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-
-        if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) {
-            sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
-            mEglSurface = null;
-            setEnabled(false);
-        }
-
-        if (surface.isValid()) {
-            if (!createSurface(surface)) {
-                return;
-            }
-
-            mUpdateDirtyRegions = true;
-
-            if (mCanvas != null) {
-                setEnabled(true);
-            }
-        }
-    }
-
-    private boolean createSurface(Surface surface) {
-        mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, surface, null);
-
-        if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
-            int error = sEgl.eglGetError();
-            if (error == EGL_BAD_NATIVE_WINDOW) {
-                Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
-                return false;
-            }
-            throw new RuntimeException("createWindowSurface failed "
-                    + GLUtils.getEGLErrorString(error));
-        }
-
-        if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
-            throw new IllegalStateException("eglMakeCurrent failed " +
-                    GLUtils.getEGLErrorString(sEgl.eglGetError()));
-        }
-
-        enableDirtyRegions();
-
-        return true;
-    }
-
-    boolean validate() {
-        return checkRenderContext() != SURFACE_STATE_ERROR;
-    }
-
-    @Override
-    void setup(int width, int height, float lightX, float lightY, float lightZ, float lightRadius) {
-        if (validate()) {
-            mCanvas.setViewport(width, height);
-            mCanvas.initializeLight(lightX, lightY, lightZ, lightRadius);
-            mWidth = width;
-            mHeight = height;
-        }
-    }
-
-    @Override
-    int getWidth() {
-        return mWidth;
-    }
-
-    @Override
-    int getHeight() {
-        return mHeight;
-    }
-
-    @Override
-    void setName(String name) {
-        mName = name;
-    }
-
-    @Override
-    void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
-            Rect dirty) {
-        if (canDraw()) {
-            if (!hasDirtyRegions()) {
-                dirty = null;
-            }
-            attachInfo.mIgnoreDirtyState = true;
-            attachInfo.mDrawingTime = SystemClock.uptimeMillis();
-
-            view.mPrivateFlags |= View.PFLAG_DRAWN;
-
-            // We are already on the correct thread
-            final int surfaceState = checkRenderContextUnsafe();
-            if (surfaceState != SURFACE_STATE_ERROR) {
-                HardwareCanvas canvas = mCanvas;
-
-                if (mProfileEnabled) {
-                    mProfileLock.lock();
-                }
-
-                dirty = beginFrame(canvas, dirty, surfaceState);
-
-                RenderNode displayList = buildDisplayList(view, canvas);
-
-                flushLayerChanges();
-
-                // buildDisplayList() calls into user code which can cause
-                // an eglMakeCurrent to happen with a different surface/context.
-                // We must therefore check again here.
-                if (checkRenderContextUnsafe() == SURFACE_STATE_ERROR) {
-                    return;
-                }
-
-                int saveCount = 0;
-                int status = RenderNode.STATUS_DONE;
-
-                long start = getSystemTime();
-                try {
-                    status = prepareFrame(dirty);
-
-                    saveCount = canvas.save();
-                    callbacks.onHardwarePreDraw(canvas);
-
-                    if (displayList != null) {
-                        status |= drawDisplayList(canvas, displayList, status);
-                    } else {
-                        // Shouldn't reach here
-                        view.draw(canvas);
-                    }
-                } catch (Exception e) {
-                    Log.e(LOG_TAG, "An error has occurred while drawing:", e);
-                } finally {
-                    callbacks.onHardwarePostDraw(canvas);
-                    canvas.restoreToCount(saveCount);
-                    view.mRecreateDisplayList = false;
-
-                    mDrawDelta = getSystemTime() - start;
-
-                    if (mDrawDelta > 0) {
-                        mFrameCount++;
-
-                        debugDirtyRegions(dirty, canvas);
-                        drawProfileData(attachInfo);
-                    }
-                }
-
-                onPostDraw();
-
-                swapBuffers(status);
-
-                if (mProfileEnabled) {
-                    mProfileLock.unlock();
-                }
-
-                attachInfo.mIgnoreDirtyState = false;
-            }
-        }
-    }
-
-    private void flushLayerChanges() {
-        // Loop through and apply any pending layer changes
-        for (int i = 0; i < mLayerUpdates.size(); i++) {
-            HardwareLayer layer = mLayerUpdates.get(i);
-            layer.flushChanges();
-            if (!layer.isValid()) {
-                // The layer was removed from mAttachedLayers, rewind i by 1
-                // Note that this shouldn't actually happen as View.getHardwareLayer()
-                // is already flushing for error checking reasons
-                i--;
-            } else if (layer.hasDisplayList()) {
-                mCanvas.pushLayerUpdate(layer);
-            }
-        }
-        mLayerUpdates.clear();
-    }
-
-    @Override
-    void fence() {
-        // Everything is immediate, so this is a no-op
-    }
-
-    private RenderNode buildDisplayList(View view, HardwareCanvas canvas) {
-        view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
-                == View.PFLAG_INVALIDATED;
-        view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
-
-        long buildDisplayListStartTime = startBuildDisplayListProfiling();
-        canvas.clearLayerUpdates();
-
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
-        RenderNode renderNode = view.getDisplayList();
-        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
-
-        endBuildDisplayListProfiling(buildDisplayListStartTime);
-
-        return renderNode;
-    }
-
-    private Rect beginFrame(HardwareCanvas canvas, Rect dirty, int surfaceState) {
-        // We had to change the current surface and/or context, redraw everything
-        if (surfaceState == SURFACE_STATE_UPDATED) {
-            dirty = null;
-            beginFrame(null);
-        } else {
-            int[] size = mSurfaceSize;
-            beginFrame(size);
-
-            if (size[1] != mHeight || size[0] != mWidth) {
-                mWidth = size[0];
-                mHeight = size[1];
-
-                canvas.setViewport(mWidth, mHeight);
-
-                dirty = null;
-            }
-        }
-
-        if (mDebugDataProvider != null) dirty = null;
-
-        return dirty;
-    }
-
-    private long startBuildDisplayListProfiling() {
-        if (mProfileEnabled) {
-            mProfileCurrentFrame += PROFILE_FRAME_DATA_COUNT;
-            if (mProfileCurrentFrame >= mProfileData.length) {
-                mProfileCurrentFrame = 0;
-            }
-
-            return System.nanoTime();
-        }
-        return 0;
-    }
-
-    private void endBuildDisplayListProfiling(long getDisplayListStartTime) {
-        if (mProfileEnabled) {
-            long now = System.nanoTime();
-            float total = (now - getDisplayListStartTime) * 0.000001f;
-            //noinspection PointlessArithmeticExpression
-            mProfileData[mProfileCurrentFrame] = total;
-        }
-    }
-
-    private int prepareFrame(Rect dirty) {
-        int status;
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "prepareFrame");
-        try {
-            status = onPreDraw(dirty);
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
-        }
-        return status;
-    }
-
-    private int drawDisplayList(HardwareCanvas canvas, RenderNode displayList,
-            int status) {
-
-        long drawDisplayListStartTime = 0;
-        if (mProfileEnabled) {
-            drawDisplayListStartTime = System.nanoTime();
-        }
-
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList");
-        nPrepareTree(displayList.getNativeDisplayList());
-        try {
-            status |= canvas.drawDisplayList(displayList, mRedrawClip,
-                    RenderNode.FLAG_CLIP_CHILDREN);
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
-        }
-
-        if (mProfileEnabled) {
-            long now = System.nanoTime();
-            float total = (now - drawDisplayListStartTime) * 0.000001f;
-            mProfileData[mProfileCurrentFrame + 1] = total;
-        }
-
-        return status;
-    }
-
-    private void swapBuffers(int status) {
-        if ((status & RenderNode.STATUS_DREW) == RenderNode.STATUS_DREW) {
-            long eglSwapBuffersStartTime = 0;
-            if (mProfileEnabled) {
-                eglSwapBuffersStartTime = System.nanoTime();
-            }
-
-            sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
-
-            if (mProfileEnabled) {
-                long now = System.nanoTime();
-                float total = (now - eglSwapBuffersStartTime) * 0.000001f;
-                mProfileData[mProfileCurrentFrame + 2] = total;
-            }
-
-            checkEglErrors();
-        }
-    }
-
-    private void debugDirtyRegions(Rect dirty, HardwareCanvas canvas) {
-        if (mDebugDirtyRegions) {
-            if (mDebugPaint == null) {
-                mDebugPaint = new Paint();
-                mDebugPaint.setColor(0x7fff0000);
-            }
-
-            if (dirty != null && (mFrameCount & 1) == 0) {
-                canvas.drawRect(dirty, mDebugPaint);
-            }
-        }
-    }
-
-    /**
-     * Ensures the current EGL context and surface are the ones we expect.
-     * This method throws an IllegalStateException if invoked from a thread
-     * that did not initialize EGL.
-     *
-     * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current,
-     *         {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or
-     *         {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one
-     *
-     * @see #checkRenderContextUnsafe()
-     */
-    int checkRenderContext() {
-        if (mEglThread != Thread.currentThread()) {
-            throw new IllegalStateException("Hardware acceleration can only be used with a " +
-                    "single UI thread.\nOriginal thread: " + mEglThread + "\n" +
-                    "Current thread: " + Thread.currentThread());
-        }
-
-        return checkRenderContextUnsafe();
-    }
-
-    /**
-     * Ensures the current EGL context and surface are the ones we expect.
-     * This method does not check the current thread.
-     *
-     * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current,
-     *         {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or
-     *         {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one
-     *
-     * @see #checkRenderContext()
-     */
-    private int checkRenderContextUnsafe() {
-        if (!mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW)) ||
-                !mEglContext.equals(sEgl.eglGetCurrentContext())) {
-            if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
-                Log.e(LOG_TAG, "eglMakeCurrent failed " +
-                        GLUtils.getEGLErrorString(sEgl.eglGetError()));
-                fallback(true);
-                return SURFACE_STATE_ERROR;
-            } else {
-                if (mUpdateDirtyRegions) {
-                    enableDirtyRegions();
-                    mUpdateDirtyRegions = false;
-                }
-                return SURFACE_STATE_UPDATED;
-            }
-        }
-        return SURFACE_STATE_SUCCESS;
-    }
-
-    private static int dpToPx(int dp, float density) {
-        return (int) (dp * density + 0.5f);
-    }
-
-    static native boolean loadProperties();
-
-    static native void setupShadersDiskCache(String cacheFile);
-
-    /**
-     * Notifies EGL that the frame is about to be rendered.
-     * @param size
-     */
-    static native void beginFrame(int[] size);
-
-    /**
-     * Returns the current system time according to the renderer.
-     * This method is used for debugging only and should not be used
-     * as a clock.
-     */
-    static native long getSystemTime();
-
-    /**
-     * Preserves the back buffer of the current surface after a buffer swap.
-     * Calling this method sets the EGL_SWAP_BEHAVIOR attribute of the current
-     * surface to EGL_BUFFER_PRESERVED. Calling this method requires an EGL
-     * config that supports EGL_SWAP_BEHAVIOR_PRESERVED_BIT.
-     *
-     * @return True if the swap behavior was successfully changed,
-     *         false otherwise.
-     */
-    static native boolean preserveBackBuffer();
-
-    /**
-     * Indicates whether the current surface preserves its back buffer
-     * after a buffer swap.
-     *
-     * @return True, if the surface's EGL_SWAP_BEHAVIOR is EGL_BUFFER_PRESERVED,
-     *         false otherwise
-     */
-    static native boolean isBackBufferPreserved();
-
-    static native void nDestroyLayer(long layerPtr);
-
-    private static native void nPrepareTree(long displayListPtr);
-
-    class GraphDataProvider {
-        /**
-         * Draws the graph as bars. Frame elements are stacked on top of
-         * each other.
-         */
-        public static final int GRAPH_TYPE_BARS = 0;
-        /**
-         * Draws the graph as lines. The number of series drawn corresponds
-         * to the number of elements.
-         */
-        public static final int GRAPH_TYPE_LINES = 1;
-
-        private final int mGraphType;
-
-        private int mVerticalUnit;
-        private int mHorizontalUnit;
-        private int mHorizontalMargin;
-        private int mThresholdStroke;
-
-        public GraphDataProvider(int graphType) {
-            mGraphType = graphType;
-        }
-
-        void prepare(DisplayMetrics metrics) {
-            final float density = metrics.density;
-
-            mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density);
-            mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density);
-            mHorizontalMargin = dpToPx(PROFILE_DRAW_MARGIN, density);
-            mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density);
-        }
-
-        int getGraphType() {
-            return mGraphType;
-        }
-
-        int getVerticalUnitSize() {
-            return mVerticalUnit;
-        }
-
-        int getHorizontalUnitSize() {
-            return mHorizontalUnit;
-        }
-
-        int getHorizontaUnitMargin() {
-            return mHorizontalMargin;
-        }
-
-        float[] getData() {
-            return mProfileData;
-        }
-
-        float getThreshold() {
-            return 16;
-        }
-
-        int getFrameCount() {
-            return mProfileData.length / PROFILE_FRAME_DATA_COUNT;
-        }
-
-        int getElementCount() {
-            return PROFILE_FRAME_DATA_COUNT;
-        }
-
-        int getCurrentFrame() {
-            return mProfileCurrentFrame / PROFILE_FRAME_DATA_COUNT;
-        }
-
-        void setupGraphPaint(Paint paint, int elementIndex) {
-            paint.setColor(PROFILE_DRAW_COLORS[elementIndex]);
-            if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke);
-        }
-
-        void setupThresholdPaint(Paint paint) {
-            paint.setColor(PROFILE_DRAW_THRESHOLD_COLOR);
-            paint.setStrokeWidth(mThresholdStroke);
-        }
-
-        void setupCurrentFramePaint(Paint paint) {
-            paint.setColor(PROFILE_DRAW_CURRENT_FRAME_COLOR);
-            if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke);
-        }
-    }
-}
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index 9568760..b8e7d8c 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -110,48 +110,6 @@
         return RenderNode.STATUS_DONE;
     }
 
-    /**
-     * Indicates that the specified layer must be updated as soon as possible.
-     *
-     * @param layer The layer to update
-     *
-     * @see #clearLayerUpdates()
-     *
-     * @hide
-     */
-    abstract void pushLayerUpdate(HardwareLayer layer);
-
-    /**
-     * Cancels a queued layer update. If the specified layer was not
-     * queued for update, this method has no effect.
-     *
-     * @param layer The layer whose update to cancel
-     *
-     * @see #pushLayerUpdate(HardwareLayer)
-     * @see #clearLayerUpdates()
-     *
-     * @hide
-     */
-    abstract void cancelLayerUpdate(HardwareLayer layer);
-
-    /**
-     * Immediately executes all enqueued layer updates.
-     *
-     * @see #pushLayerUpdate(HardwareLayer)
-     *
-     * @hide
-     */
-    abstract void flushLayerUpdates();
-
-    /**
-     * Removes all enqueued layer updates.
-     *
-     * @see #pushLayerUpdate(HardwareLayer)
-     *
-     * @hide
-     */
-    abstract void clearLayerUpdates();
-
     public abstract void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
             CanvasProperty<Float> radius, CanvasProperty<Paint> paint);
 }
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index 652bcd2..b5b9199 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -172,24 +172,6 @@
         });
     }
 
-    /**
-     * This exists to minimize impact into the current HardwareLayer paths as
-     * some of the specifics of how to handle error cases in the fully
-     * deferred model will work
-     */
-    @Deprecated
-    public void flushChanges() {
-        if (HardwareRenderer.sUseRenderThread) {
-            // Not supported, don't try.
-            return;
-        }
-
-        boolean success = nFlushChanges(mFinalizer.get());
-        if (!success) {
-            destroy();
-        }
-    }
-
     public long getLayer() {
         return nGetLayer(mFinalizer.get());
     }
@@ -216,33 +198,14 @@
         return st;
     }
 
-    /**
-     * This should only be used by HardwareRenderer! Do not call directly
-     */
-    static HardwareLayer createTextureLayer(HardwareRenderer renderer) {
-        return new HardwareLayer(renderer, nCreateTextureLayer(), LAYER_TYPE_TEXTURE);
-    }
-
     static HardwareLayer adoptTextureLayer(HardwareRenderer renderer, long layer) {
         return new HardwareLayer(renderer, layer, LAYER_TYPE_TEXTURE);
     }
 
-    /**
-     * This should only be used by HardwareRenderer! Do not call directly
-     */
-    static HardwareLayer createDisplayListLayer(HardwareRenderer renderer,
-            int width, int height) {
-        return new HardwareLayer(renderer, nCreateRenderLayer(width, height), LAYER_TYPE_DISPLAY_LIST);
-    }
-
     static HardwareLayer adoptDisplayListLayer(HardwareRenderer renderer, long layer) {
         return new HardwareLayer(renderer, layer, LAYER_TYPE_DISPLAY_LIST);
     }
 
-    /** This also creates the underlying layer */
-    private static native long nCreateTextureLayer();
-    private static native long nCreateRenderLayer(int width, int height);
-
     private static native void nOnTextureDestroyed(long layerUpdater);
 
     private static native boolean nPrepare(long layerUpdater, int width, int height, boolean isOpaque);
@@ -254,8 +217,6 @@
     private static native void nUpdateRenderLayer(long layerUpdater, long displayList,
             int left, int top, int right, int bottom);
 
-    private static native boolean nFlushChanges(long layerUpdater);
-
     private static native long nGetLayer(long layerUpdater);
     private static native int nGetTexName(long layerUpdater);
 }
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index d71de9f..592dec8 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -171,9 +171,6 @@
      */
     public static boolean sSystemRendererDisabled = false;
 
-    /** @hide */
-    public static boolean sUseRenderThread = true;
-
     private boolean mEnabled;
     private boolean mRequested = true;
 
@@ -309,7 +306,7 @@
      * @hide
      */
     public static void setupDiskCache(File cacheDir) {
-        GLRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
+        ThreadedRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
     }
 
     /**
@@ -366,8 +363,7 @@
      * @param callbacks Callbacks invoked when drawing happens.
      * @param dirty The dirty rectangle to update, can be null.
      */
-    abstract void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
-            Rect dirty);
+    abstract void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks);
 
     /**
      * Creates a new hardware layer. A hardware layer built by calling this
@@ -469,11 +465,7 @@
     static HardwareRenderer create(boolean translucent) {
         HardwareRenderer renderer = null;
         if (GLES20Canvas.isAvailable()) {
-            if (sUseRenderThread) {
-                renderer = new ThreadedRenderer(translucent);
-            } else {
-                renderer = new GLRenderer(translucent);
-            }
+            renderer = new ThreadedRenderer(translucent);
         }
         return renderer;
     }
@@ -500,7 +492,7 @@
      *              see {@link android.content.ComponentCallbacks}
      */
     static void startTrimMemory(int level) {
-        GLRenderer.startTrimMemory(level);
+        ThreadedRenderer.startTrimMemory(level);
     }
 
     /**
@@ -508,7 +500,7 @@
      * cleanup special resources used by the memory trimming process.
      */
     static void endTrimMemory() {
-        GLRenderer.endTrimMemory();
+        ThreadedRenderer.endTrimMemory();
     }
 
     /**
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index c32a2c9..fa5bd88 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -46,7 +46,7 @@
     int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,
             in int viewVisibility, in int layerStackId, out Rect outContentInsets);
     void remove(IWindow window);
-    
+
     /**
      * Change the parameters of a window.  You supply the
      * new parameters, it returns the new frame of the window on screen (the
@@ -109,7 +109,7 @@
      * to optimize compositing of this part of the window.
      */
     void setTransparentRegion(IWindow window, in Region region);
-    
+
     /**
      * Tell the window manager about the content and visible insets of the
      * given window, which can be used to adjust the <var>outContentInsets</var>
@@ -122,20 +122,20 @@
      */
     void setInsets(IWindow window, int touchableInsets, in Rect contentInsets,
             in Rect visibleInsets, in Region touchableRegion);
-    
+
     /**
      * Return the current display size in which the window is being laid out,
      * accounting for screen decorations around it.
      */
     void getDisplayFrame(IWindow window, out Rect outDisplayFrame);
-    
+
     void finishDrawing(IWindow window);
-    
+
     void setInTouchMode(boolean showFocus);
     boolean getInTouchMode();
-    
+
     boolean performHapticFeedback(IWindow window, int effectId, boolean always);
-    
+
     /**
      * Allocate the drag's thumbnail surface.  Also assigns a token that identifies
      * the drag to the OS and passes that as the return value.  A return value of
@@ -150,11 +150,11 @@
     boolean performDrag(IWindow window, IBinder dragToken, float touchX, float touchY,
             float thumbCenterX, float thumbCenterY, in ClipData data);
 
-	/**
-	 * Report the result of a drop action targeted to the given window.
-	 * consumed is 'true' when the drop was accepted by a valid recipient,
-	 * 'false' otherwise.
-	 */
+   /**
+     * Report the result of a drop action targeted to the given window.
+     * consumed is 'true' when the drop was accepted by a valid recipient,
+     * 'false' otherwise.
+     */
 	void reportDropResult(IWindow window, boolean consumed);
 
     /**
@@ -174,12 +174,12 @@
      * how big the increment is from one screen to another.
      */
     void setWallpaperPosition(IBinder windowToken, float x, float y, float xstep, float ystep);
-    
+
     void wallpaperOffsetsComplete(IBinder window);
-    
+
     Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
             int z, in Bundle extras, boolean sync);
-    
+
     void wallpaperCommandComplete(IBinder window, in Bundle result);
 
     void setUniverseTransform(IBinder window, float alpha, float offx, float offy,
@@ -188,7 +188,7 @@
     /**
      * Notifies that a rectangle on the screen has been requested.
      */
-    void onRectangleOnScreenRequested(IBinder token, in Rect rectangle, boolean immediate);
+    void onRectangleOnScreenRequested(IBinder token, in Rect rectangle);
 
     IWindowId getWindowId(IBinder window);
 }
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 8a996d2..8b2ec7a 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -1716,6 +1716,7 @@
             case KeyEvent.KEYCODE_MENU:
             case KeyEvent.KEYCODE_SLEEP:
             case KeyEvent.KEYCODE_WAKEUP:
+            case KeyEvent.KEYCODE_PAIRING:
                 return true;
         }
         return false;
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index e63829e..c165475 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -325,8 +325,8 @@
      *
      * @hide
      */
-    public void setCaching(boolean caching) {
-        nSetCaching(mNativeRenderNode, caching);
+    public boolean setCaching(boolean caching) {
+        return nSetCaching(mNativeRenderNode, caching);
     }
 
     /**
@@ -335,8 +335,8 @@
      *
      * @param clipToBounds true if the display list should clip to its bounds
      */
-    public void setClipToBounds(boolean clipToBounds) {
-        nSetClipToBounds(mNativeRenderNode, clipToBounds);
+    public boolean setClipToBounds(boolean clipToBounds) {
+        return nSetClipToBounds(mNativeRenderNode, clipToBounds);
     }
 
     /**
@@ -346,8 +346,8 @@
      * @param shouldProject true if the display list should be projected onto a
      *            containing volume.
      */
-    public void setProjectBackwards(boolean shouldProject) {
-        nSetProjectBackwards(mNativeRenderNode, shouldProject);
+    public boolean setProjectBackwards(boolean shouldProject) {
+        return nSetProjectBackwards(mNativeRenderNode, shouldProject);
     }
 
     /**
@@ -355,8 +355,8 @@
      * DisplayList should draw any descendent DisplayLists with
      * ProjectBackwards=true directly on top of it. Default value is false.
      */
-    public void setProjectionReceiver(boolean shouldRecieve) {
-        nSetProjectionReceiver(mNativeRenderNode, shouldRecieve);
+    public boolean setProjectionReceiver(boolean shouldRecieve) {
+        return nSetProjectionReceiver(mNativeRenderNode, shouldRecieve);
     }
 
     /**
@@ -365,15 +365,16 @@
      *
      * Deep copies the data into native to simplify reference ownership.
      */
-    public void setOutline(Outline outline) {
+    public boolean setOutline(Outline outline) {
         if (outline == null || outline.isEmpty()) {
-            nSetOutlineEmpty(mNativeRenderNode);
+            return nSetOutlineEmpty(mNativeRenderNode);
         } else if (outline.mRect != null) {
-            nSetOutlineRoundRect(mNativeRenderNode, outline.mRect.left, outline.mRect.top,
+            return nSetOutlineRoundRect(mNativeRenderNode, outline.mRect.left, outline.mRect.top,
                     outline.mRect.right, outline.mRect.bottom, outline.mRadius);
         } else if (outline.mPath != null) {
-            nSetOutlineConvexPath(mNativeRenderNode, outline.mPath.mNativePath);
+            return nSetOutlineConvexPath(mNativeRenderNode, outline.mPath.mNativePath);
         }
+        throw new IllegalArgumentException("Unrecognized outline?");
     }
 
     /**
@@ -381,8 +382,8 @@
      *
      * @param clipToOutline true if clipping to the outline.
      */
-    public void setClipToOutline(boolean clipToOutline) {
-        nSetClipToOutline(mNativeRenderNode, clipToOutline);
+    public boolean setClipToOutline(boolean clipToOutline) {
+        return nSetClipToOutline(mNativeRenderNode, clipToOutline);
     }
 
     public boolean getClipToOutline() {
@@ -392,9 +393,9 @@
     /**
      * Controls the RenderNode's circular reveal clip.
      */
-    public void setRevealClip(boolean shouldClip, boolean inverseClip,
+    public boolean setRevealClip(boolean shouldClip, boolean inverseClip,
             float x, float y, float radius) {
-        nSetRevealClip(mNativeRenderNode, shouldClip, inverseClip, x, y, radius);
+        return nSetRevealClip(mNativeRenderNode, shouldClip, inverseClip, x, y, radius);
     }
 
     /**
@@ -403,8 +404,8 @@
      *
      * @param matrix A transform matrix to apply to this display list
      */
-    public void setStaticMatrix(Matrix matrix) {
-        nSetStaticMatrix(mNativeRenderNode, matrix.native_instance);
+    public boolean setStaticMatrix(Matrix matrix) {
+        return nSetStaticMatrix(mNativeRenderNode, matrix.native_instance);
     }
 
     /**
@@ -417,8 +418,8 @@
      *
      * @hide
      */
-    public void setAnimationMatrix(Matrix matrix) {
-        nSetAnimationMatrix(mNativeRenderNode,
+    public boolean setAnimationMatrix(Matrix matrix) {
+        return nSetAnimationMatrix(mNativeRenderNode,
                 (matrix != null) ? matrix.native_instance : 0);
     }
 
@@ -430,8 +431,8 @@
      * @see View#setAlpha(float)
      * @see #getAlpha()
      */
-    public void setAlpha(float alpha) {
-        nSetAlpha(mNativeRenderNode, alpha);
+    public boolean setAlpha(float alpha) {
+        return nSetAlpha(mNativeRenderNode, alpha);
     }
 
     /**
@@ -456,8 +457,8 @@
      * @see android.view.View#hasOverlappingRendering()
      * @see #hasOverlappingRendering()
      */
-    public void setHasOverlappingRendering(boolean hasOverlappingRendering) {
-        nSetHasOverlappingRendering(mNativeRenderNode, hasOverlappingRendering);
+    public boolean setHasOverlappingRendering(boolean hasOverlappingRendering) {
+        return nSetHasOverlappingRendering(mNativeRenderNode, hasOverlappingRendering);
     }
 
     /**
@@ -472,8 +473,8 @@
         return nHasOverlappingRendering(mNativeRenderNode);
     }
 
-    public void setElevation(float lift) {
-        nSetElevation(mNativeRenderNode, lift);
+    public boolean setElevation(float lift) {
+        return nSetElevation(mNativeRenderNode, lift);
     }
 
     public float getElevation() {
@@ -488,8 +489,8 @@
      * @see View#setTranslationX(float)
      * @see #getTranslationX()
      */
-    public void setTranslationX(float translationX) {
-        nSetTranslationX(mNativeRenderNode, translationX);
+    public boolean setTranslationX(float translationX) {
+        return nSetTranslationX(mNativeRenderNode, translationX);
     }
 
     /**
@@ -509,8 +510,8 @@
      * @see View#setTranslationY(float)
      * @see #getTranslationY()
      */
-    public void setTranslationY(float translationY) {
-        nSetTranslationY(mNativeRenderNode, translationY);
+    public boolean setTranslationY(float translationY) {
+        return nSetTranslationY(mNativeRenderNode, translationY);
     }
 
     /**
@@ -528,8 +529,8 @@
      * @see View#setTranslationZ(float)
      * @see #getTranslationZ()
      */
-    public void setTranslationZ(float translationZ) {
-        nSetTranslationZ(mNativeRenderNode, translationZ);
+    public boolean setTranslationZ(float translationZ) {
+        return nSetTranslationZ(mNativeRenderNode, translationZ);
     }
 
     /**
@@ -549,8 +550,8 @@
      * @see View#setRotation(float)
      * @see #getRotation()
      */
-    public void setRotation(float rotation) {
-        nSetRotation(mNativeRenderNode, rotation);
+    public boolean setRotation(float rotation) {
+        return nSetRotation(mNativeRenderNode, rotation);
     }
 
     /**
@@ -570,8 +571,8 @@
      * @see View#setRotationX(float)
      * @see #getRotationX()
      */
-    public void setRotationX(float rotationX) {
-        nSetRotationX(mNativeRenderNode, rotationX);
+    public boolean setRotationX(float rotationX) {
+        return nSetRotationX(mNativeRenderNode, rotationX);
     }
 
     /**
@@ -591,8 +592,8 @@
      * @see View#setRotationY(float)
      * @see #getRotationY()
      */
-    public void setRotationY(float rotationY) {
-        nSetRotationY(mNativeRenderNode, rotationY);
+    public boolean setRotationY(float rotationY) {
+        return nSetRotationY(mNativeRenderNode, rotationY);
     }
 
     /**
@@ -612,8 +613,8 @@
      * @see View#setScaleX(float)
      * @see #getScaleX()
      */
-    public void setScaleX(float scaleX) {
-        nSetScaleX(mNativeRenderNode, scaleX);
+    public boolean setScaleX(float scaleX) {
+        return nSetScaleX(mNativeRenderNode, scaleX);
     }
 
     /**
@@ -633,8 +634,8 @@
      * @see View#setScaleY(float)
      * @see #getScaleY()
      */
-    public void setScaleY(float scaleY) {
-        nSetScaleY(mNativeRenderNode, scaleY);
+    public boolean setScaleY(float scaleY) {
+        return nSetScaleY(mNativeRenderNode, scaleY);
     }
 
     /**
@@ -654,8 +655,8 @@
      * @see View#setPivotX(float)
      * @see #getPivotX()
      */
-    public void setPivotX(float pivotX) {
-        nSetPivotX(mNativeRenderNode, pivotX);
+    public boolean setPivotX(float pivotX) {
+        return nSetPivotX(mNativeRenderNode, pivotX);
     }
 
     /**
@@ -675,8 +676,8 @@
      * @see View#setPivotY(float)
      * @see #getPivotY()
      */
-    public void setPivotY(float pivotY) {
-        nSetPivotY(mNativeRenderNode, pivotY);
+    public boolean setPivotY(float pivotY) {
+        return nSetPivotY(mNativeRenderNode, pivotY);
     }
 
     /**
@@ -702,8 +703,8 @@
      * @see View#setCameraDistance(float)
      * @see #getCameraDistance()
      */
-    public void setCameraDistance(float distance) {
-        nSetCameraDistance(mNativeRenderNode, distance);
+    public boolean setCameraDistance(float distance) {
+        return nSetCameraDistance(mNativeRenderNode, distance);
     }
 
     /**
@@ -723,8 +724,8 @@
      * @see View#setLeft(int)
      * @see #getLeft()
      */
-    public void setLeft(int left) {
-        nSetLeft(mNativeRenderNode, left);
+    public boolean setLeft(int left) {
+        return nSetLeft(mNativeRenderNode, left);
     }
 
     /**
@@ -744,8 +745,8 @@
      * @see View#setTop(int)
      * @see #getTop()
      */
-    public void setTop(int top) {
-        nSetTop(mNativeRenderNode, top);
+    public boolean setTop(int top) {
+        return nSetTop(mNativeRenderNode, top);
     }
 
     /**
@@ -765,8 +766,8 @@
      * @see View#setRight(int)
      * @see #getRight()
      */
-    public void setRight(int right) {
-        nSetRight(mNativeRenderNode, right);
+    public boolean setRight(int right) {
+        return nSetRight(mNativeRenderNode, right);
     }
 
     /**
@@ -786,8 +787,8 @@
      * @see View#setBottom(int)
      * @see #getBottom()
      */
-    public void setBottom(int bottom) {
-        nSetBottom(mNativeRenderNode, bottom);
+    public boolean setBottom(int bottom) {
+        return nSetBottom(mNativeRenderNode, bottom);
     }
 
     /**
@@ -812,8 +813,8 @@
      * @see View#setRight(int)
      * @see View#setBottom(int)
      */
-    public void setLeftTopRightBottom(int left, int top, int right, int bottom) {
-        nSetLeftTopRightBottom(mNativeRenderNode, left, top, right, bottom);
+    public boolean setLeftTopRightBottom(int left, int top, int right, int bottom) {
+        return nSetLeftTopRightBottom(mNativeRenderNode, left, top, right, bottom);
     }
 
     /**
@@ -824,8 +825,8 @@
      *
      * @see View#offsetLeftAndRight(int)
      */
-    public void offsetLeftAndRight(float offset) {
-        nOffsetLeftAndRight(mNativeRenderNode, offset);
+    public boolean offsetLeftAndRight(float offset) {
+        return nOffsetLeftAndRight(mNativeRenderNode, offset);
     }
 
     /**
@@ -836,8 +837,8 @@
      *
      * @see View#offsetTopAndBottom(int)
      */
-    public void offsetTopAndBottom(float offset) {
-        nOffsetTopAndBottom(mNativeRenderNode, offset);
+    public boolean offsetTopAndBottom(float offset) {
+        return nOffsetTopAndBottom(mNativeRenderNode, offset);
     }
 
     /**
@@ -890,42 +891,42 @@
 
     // Properties
 
-    private static native void nOffsetTopAndBottom(long renderNode, float offset);
-    private static native void nOffsetLeftAndRight(long renderNode, float offset);
-    private static native void nSetLeftTopRightBottom(long renderNode, int left, int top,
+    private static native boolean nOffsetTopAndBottom(long renderNode, float offset);
+    private static native boolean nOffsetLeftAndRight(long renderNode, float offset);
+    private static native boolean nSetLeftTopRightBottom(long renderNode, int left, int top,
             int right, int bottom);
-    private static native void nSetBottom(long renderNode, int bottom);
-    private static native void nSetRight(long renderNode, int right);
-    private static native void nSetTop(long renderNode, int top);
-    private static native void nSetLeft(long renderNode, int left);
-    private static native void nSetCameraDistance(long renderNode, float distance);
-    private static native void nSetPivotY(long renderNode, float pivotY);
-    private static native void nSetPivotX(long renderNode, float pivotX);
-    private static native void nSetCaching(long renderNode, boolean caching);
-    private static native void nSetClipToBounds(long renderNode, boolean clipToBounds);
-    private static native void nSetProjectBackwards(long renderNode, boolean shouldProject);
-    private static native void nSetProjectionReceiver(long renderNode, boolean shouldRecieve);
-    private static native void nSetOutlineRoundRect(long renderNode, int left, int top,
+    private static native boolean nSetBottom(long renderNode, int bottom);
+    private static native boolean nSetRight(long renderNode, int right);
+    private static native boolean nSetTop(long renderNode, int top);
+    private static native boolean nSetLeft(long renderNode, int left);
+    private static native boolean nSetCameraDistance(long renderNode, float distance);
+    private static native boolean nSetPivotY(long renderNode, float pivotY);
+    private static native boolean nSetPivotX(long renderNode, float pivotX);
+    private static native boolean nSetCaching(long renderNode, boolean caching);
+    private static native boolean nSetClipToBounds(long renderNode, boolean clipToBounds);
+    private static native boolean nSetProjectBackwards(long renderNode, boolean shouldProject);
+    private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldRecieve);
+    private static native boolean nSetOutlineRoundRect(long renderNode, int left, int top,
             int right, int bottom, float radius);
-    private static native void nSetOutlineConvexPath(long renderNode, long nativePath);
-    private static native void nSetOutlineEmpty(long renderNode);
-    private static native void nSetClipToOutline(long renderNode, boolean clipToOutline);
-    private static native void nSetRevealClip(long renderNode,
+    private static native boolean nSetOutlineConvexPath(long renderNode, long nativePath);
+    private static native boolean nSetOutlineEmpty(long renderNode);
+    private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline);
+    private static native boolean nSetRevealClip(long renderNode,
             boolean shouldClip, boolean inverseClip, float x, float y, float radius);
-    private static native void nSetAlpha(long renderNode, float alpha);
-    private static native void nSetHasOverlappingRendering(long renderNode,
+    private static native boolean nSetAlpha(long renderNode, float alpha);
+    private static native boolean nSetHasOverlappingRendering(long renderNode,
             boolean hasOverlappingRendering);
-    private static native void nSetElevation(long renderNode, float lift);
-    private static native void nSetTranslationX(long renderNode, float translationX);
-    private static native void nSetTranslationY(long renderNode, float translationY);
-    private static native void nSetTranslationZ(long renderNode, float translationZ);
-    private static native void nSetRotation(long renderNode, float rotation);
-    private static native void nSetRotationX(long renderNode, float rotationX);
-    private static native void nSetRotationY(long renderNode, float rotationY);
-    private static native void nSetScaleX(long renderNode, float scaleX);
-    private static native void nSetScaleY(long renderNode, float scaleY);
-    private static native void nSetStaticMatrix(long renderNode, long nativeMatrix);
-    private static native void nSetAnimationMatrix(long renderNode, long animationMatrix);
+    private static native boolean nSetElevation(long renderNode, float lift);
+    private static native boolean nSetTranslationX(long renderNode, float translationX);
+    private static native boolean nSetTranslationY(long renderNode, float translationY);
+    private static native boolean nSetTranslationZ(long renderNode, float translationZ);
+    private static native boolean nSetRotation(long renderNode, float rotation);
+    private static native boolean nSetRotationX(long renderNode, float rotationX);
+    private static native boolean nSetRotationY(long renderNode, float rotationY);
+    private static native boolean nSetScaleX(long renderNode, float scaleX);
+    private static native boolean nSetScaleY(long renderNode, float scaleY);
+    private static native boolean nSetStaticMatrix(long renderNode, long nativeMatrix);
+    private static native boolean nSetAnimationMatrix(long renderNode, long animationMatrix);
 
     private static native boolean nHasOverlappingRendering(long renderNode);
     private static native boolean nGetClipToOutline(long renderNode);
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c15ce44..79f19b5 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -38,11 +38,11 @@
     private static native void nativeDestroy(long nativeObject);
 
     private static native Bitmap nativeScreenshot(IBinder displayToken,
-            int width, int height, int minLayer, int maxLayer, boolean allLayers,
-            boolean useIdentityTransform);
+            Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
+            boolean allLayers, boolean useIdentityTransform);
     private static native void nativeScreenshot(IBinder displayToken, Surface consumer,
-            int width, int height, int minLayer, int maxLayer, boolean allLayers,
-            boolean useIdentityTransform);
+            Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
+            boolean allLayers, boolean useIdentityTransform);
 
     private static native void nativeOpenTransaction();
     private static native void nativeCloseTransaction();
@@ -78,8 +78,8 @@
             IBinder displayToken);
     private static native int nativeGetActiveConfig(IBinder displayToken);
     private static native boolean nativeSetActiveConfig(IBinder displayToken, int id);
-    private static native void nativeBlankDisplay(IBinder displayToken);
-    private static native void nativeUnblankDisplay(IBinder displayToken);
+    private static native void nativeSetDisplayPowerMode(
+            IBinder displayToken, int mode);
 
 
     private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -209,6 +209,25 @@
      */
     public static final int BUILT_IN_DISPLAY_ID_HDMI = 1;
 
+    /* Display power modes * /
+
+    /**
+     * Display power mode off: used while blanking the screen.
+     * Use only with {@link SurfaceControl#setDisplayPowerMode()}.
+     */
+    public static final int POWER_MODE_OFF = 0;
+
+    /**
+     * Display power mode doze: used while putting the screen into low power mode.
+     * Use only with {@link SurfaceControl#setDisplayPowerMode()}.
+     */
+    public static final int POWER_MODE_DOZE = 1;
+
+    /**
+     * Display power mode normal: used while unblanking the screen.
+     * Use only with {@link SurfaceControl#setDisplayPowerMode()}.
+     */
+    public static final int POWER_MODE_NORMAL = 2;
 
 
     /**
@@ -487,18 +506,11 @@
         }
     }
 
-    public static void unblankDisplay(IBinder displayToken) {
+    public static void setDisplayPowerMode(IBinder displayToken, int mode) {
         if (displayToken == null) {
             throw new IllegalArgumentException("displayToken must not be null");
         }
-        nativeUnblankDisplay(displayToken);
-    }
-
-    public static void blankDisplay(IBinder displayToken) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
-        }
-        nativeBlankDisplay(displayToken);
+        nativeSetDisplayPowerMode(displayToken, mode);
     }
 
     public static SurfaceControl.PhysicalDisplayInfo[] getDisplayConfigs(IBinder displayToken) {
@@ -597,8 +609,8 @@
     public static void screenshot(IBinder display, Surface consumer,
             int width, int height, int minLayer, int maxLayer,
             boolean useIdentityTransform) {
-        screenshot(display, consumer, width, height, minLayer, maxLayer, false,
-                useIdentityTransform);
+        screenshot(display, consumer, new Rect(), width, height, minLayer, maxLayer,
+                false, useIdentityTransform);
     }
 
     /**
@@ -613,7 +625,7 @@
      */
     public static void screenshot(IBinder display, Surface consumer,
             int width, int height) {
-        screenshot(display, consumer, width, height, 0, 0, true, false);
+        screenshot(display, consumer, new Rect(), width, height, 0, 0, true, false);
     }
 
     /**
@@ -623,7 +635,7 @@
      * @param consumer The {@link Surface} to take the screenshot into.
      */
     public static void screenshot(IBinder display, Surface consumer) {
-        screenshot(display, consumer, 0, 0, 0, 0, true, false);
+        screenshot(display, consumer, new Rect(), 0, 0, 0, 0, true, false);
     }
 
     /**
@@ -634,6 +646,8 @@
      * the versions that use a {@link Surface} instead, such as
      * {@link SurfaceControl#screenshot(IBinder, Surface)}.
      *
+     * @param sourceCrop The portion of the screen to capture into the Bitmap;
+     * caller may pass in 'new Rect()' if no cropping is desired.
      * @param width The desired width of the returned bitmap; the raw
      * screen will be scaled down to this size.
      * @param height The desired height of the returned bitmap; the raw
@@ -649,13 +663,13 @@
      * if an error occurs. Make sure to call Bitmap.recycle() as soon as
      * possible, once its content is not needed anymore.
      */
-    public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer,
-            boolean useIdentityTransform) {
+    public static Bitmap screenshot(Rect sourceCrop, int width, int height,
+            int minLayer, int maxLayer, boolean useIdentityTransform) {
         // TODO: should take the display as a parameter
         IBinder displayToken = SurfaceControl.getBuiltInDisplay(
                 SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
-        return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false,
-                useIdentityTransform);
+        return nativeScreenshot(displayToken, sourceCrop, width, height,
+                minLayer, maxLayer, false, useIdentityTransform);
     }
 
     /**
@@ -674,10 +688,10 @@
         // TODO: should take the display as a parameter
         IBinder displayToken = SurfaceControl.getBuiltInDisplay(
                 SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
-        return nativeScreenshot(displayToken, width, height, 0, 0, true, false);
+        return nativeScreenshot(displayToken, new Rect(), width, height, 0, 0, true, false);
     }
 
-    private static void screenshot(IBinder display, Surface consumer,
+    private static void screenshot(IBinder display, Surface consumer, Rect sourceCrop,
             int width, int height, int minLayer, int maxLayer, boolean allLayers,
             boolean useIdentityTransform) {
         if (display == null) {
@@ -686,7 +700,7 @@
         if (consumer == null) {
             throw new IllegalArgumentException("consumer must not be null");
         }
-        nativeScreenshot(display, consumer, width, height, minLayer, maxLayer, allLayers,
-                useIdentityTransform);
+        nativeScreenshot(display, consumer, sourceCrop, width, height,
+                minLayer, maxLayer, allLayers, useIdentityTransform);
     }
 }
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 72b9d3e..7bbe84e 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -54,8 +54,6 @@
 public class ThreadedRenderer extends HardwareRenderer {
     private static final String LOGTAG = "ThreadedRenderer";
 
-    private static final Rect NULL_RECT = new Rect();
-
     // Keep in sync with DrawFrameTask.h SYNC_* flags
     // Nothing interesting to report
     private static final int SYNC_OK = 0x0;
@@ -74,8 +72,7 @@
     private boolean mProfilingEnabled;
 
     ThreadedRenderer(boolean translucent) {
-        // Temporarily disabled
-        //AtlasInitializer.sInstance.init();
+        AtlasInitializer.sInstance.init();
 
         long rootNodePtr = nCreateRootRenderNode();
         mRootNode = RenderNode.adopt(rootNodePtr);
@@ -229,7 +226,7 @@
     }
 
     @Override
-    void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty) {
+    void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
         attachInfo.mIgnoreDirtyState = true;
         long frameTimeNanos = mChoreographer.getFrameTimeNanos();
         attachInfo.mDrawingTime = frameTimeNanos / TimeUtils.NANOS_PER_MS;
@@ -247,12 +244,8 @@
 
         attachInfo.mIgnoreDirtyState = false;
 
-        if (dirty == null) {
-            dirty = NULL_RECT;
-        }
         int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos,
-                recordDuration, view.getResources().getDisplayMetrics().density,
-                dirty.left, dirty.top, dirty.right, dirty.bottom);
+                recordDuration, view.getResources().getDisplayMetrics().density);
         if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) {
             attachInfo.mViewRootImpl.invalidate();
         }
@@ -332,6 +325,14 @@
         }
     }
 
+    static void startTrimMemory(int level) {
+        // TODO
+    }
+
+    static void endTrimMemory() {
+        // TODO
+    }
+
     private static class AtlasInitializer {
         static AtlasInitializer sInstance = new AtlasInitializer();
 
@@ -368,6 +369,8 @@
         }
     }
 
+    static native void setupShadersDiskCache(String cacheFile);
+
     private static native void nSetAtlas(GraphicBuffer buffer, long[] map);
 
     private static native long nCreateRootRenderNode();
@@ -384,8 +387,7 @@
             float lightX, float lightY, float lightZ, float lightRadius);
     private static native void nSetOpaque(long nativeProxy, boolean opaque);
     private static native int nSyncAndDrawFrame(long nativeProxy,
-            long frameTimeNanos, long recordDuration, float density,
-            int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom);
+            long frameTimeNanos, long recordDuration, float density);
     private static native void nRunWithGlContext(long nativeProxy, Runnable runnable);
     private static native void nDestroyCanvasAndSurface(long nativeProxy);
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7c70ee4..21bae23 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -431,7 +431,7 @@
  * child. The child must use this size, and guarantee that all of its
  * descendants will fit within this size.
  * <li>AT_MOST: This is used by the parent to impose a maximum size on the
- * child. The child must gurantee that it and all of its descendants will fit
+ * child. The child must guarantee that it and all of its descendants will fit
  * within this size.
  * </ul>
  * </p>
@@ -2775,6 +2775,13 @@
     /**
      * @hide
      *
+     * Whether Recents is visible or not.
+     */
+    public static final int RECENT_APPS_VISIBLE = 0x00004000;
+
+    /**
+     * @hide
+     *
      * Makes system ui transparent.
      */
     public static final int SYSTEM_UI_TRANSPARENT = 0x00008000;
@@ -2782,7 +2789,7 @@
     /**
      * @hide
      */
-    public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x00007FFF;
+    public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x00003FFF;
 
     /**
      * These are the system UI flags that can be cleared by events outside
@@ -5377,8 +5384,9 @@
      * Gets the location of this view in screen coordintates.
      *
      * @param outRect The output location
+     * @hide
      */
-    void getBoundsOnScreen(Rect outRect) {
+    public void getBoundsOnScreen(Rect outRect) {
         if (mAttachInfo == null) {
             return;
         }
@@ -7648,7 +7656,7 @@
      * notification is at at most once every
      * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}
      * to avoid unnecessary load to the system. Also once a view has a pending
-     * notifucation this method is a NOP until the notification has been sent.
+     * notification this method is a NOP until the notification has been sent.
      *
      * @hide
      */
@@ -9236,6 +9244,30 @@
     }
 
     /**
+     * Request unbuffered dispatch of the given stream of MotionEvents to this View.
+     *
+     * Until this View receives a corresponding {@link MotionEvent#ACTION_UP}, ask that the input
+     * system not batch {@link MotionEvent}s but instead deliver them as soon as they're
+     * available. This method should only be called for touch events.
+     *
+     * <p class="note">This api is not intended for most applications. Buffered dispatch
+     * provides many of benefits, and just requesting unbuffered dispatch on most MotionEvent
+     * streams will not improve your input latency. Side effects include: increased latency,
+     * jittery scrolls and inability to take advantage of system resampling. Talk to your input
+     * professional to see if {@link #requestUnbufferedDispatch(MotionEvent)} is right for
+     * you.</p>
+     */
+    public final void requestUnbufferedDispatch(MotionEvent event) {
+        final int action = event.getAction();
+        if (mAttachInfo == null
+                || action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_MOVE
+                || !event.isTouchEvent()) {
+            return;
+        }
+        mAttachInfo.mUnbufferedDispatchRequested = true;
+    }
+
+    /**
      * Set flags controlling behavior of this view.
      *
      * @param flags Constant indicating the value which should be set
@@ -9646,7 +9678,7 @@
 
     /**
      * The transform matrix of this view, which is calculated based on the current
-     * roation, scale, and pivot properties.
+     * rotation, scale, and pivot properties.
      *
      * @see #getRotation()
      * @see #getScaleX()
@@ -13556,12 +13588,6 @@
                 }
             }
 
-            // The layer is not valid if the underlying GPU resources cannot be allocated
-            mHardwareLayer.flushChanges();
-            if (!mHardwareLayer.isValid()) {
-                return null;
-            }
-
             mHardwareLayer.setLayerPaint(mLayerPaint);
             RenderNode displayList = mHardwareLayer.startRecording();
             updateDisplayListIfDirty(displayList, true);
@@ -19761,6 +19787,12 @@
         boolean mInTouchMode;
 
         /**
+         * Indicates whether the view has requested unbuffered input dispatching for the current
+         * event stream.
+         */
+        boolean mUnbufferedDispatchRequested;
+
+        /**
          * Indicates that ViewAncestor should trigger a global layout change
          * the next time it performs a traversal
          */
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index eef09ae..b29f6d4 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4530,6 +4530,28 @@
     }
 
     /**
+     * Native-calculated damage path
+     * Returns false if this path was unable to complete successfully. This means
+     * it hit a ViewParent it doesn't recognize and needs to fall back to calculating
+     * damage area
+     * @hide
+     */
+    public boolean damageChildDeferred(View child) {
+        ViewParent parent = getParent();
+        while (parent != null) {
+            if (parent instanceof ViewGroup) {
+                parent = parent.getParent();
+            } else if (parent instanceof ViewRootImpl) {
+                ((ViewRootImpl) parent).invalidate();
+                return true;
+            } else {
+                parent = null;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Quick invalidation method called by View.invalidateViewProperty. This doesn't set the
      * DRAWN flags and doesn't handle the Animation logic that the default invalidation methods
      * do; all we want to do here is schedule a traversal with the appropriate dirty rect.
@@ -4537,6 +4559,10 @@
      * @hide
      */
     public void damageChild(View child, final Rect dirty) {
+        if (damageChildDeferred(child)) {
+            return;
+        }
+
         ViewParent parent = this;
 
         final AttachInfo attachInfo = mAttachInfo;
@@ -6913,13 +6939,9 @@
             if (getClass() != another.getClass()) {
                 return 1;
             }
-            // First is above second.
-            if (mLocation.bottom - another.mLocation.top <= 0) {
-                return -1;
-            }
-            // First is below second.
-            if (mLocation.top - another.mLocation.bottom >= 0) {
-                return 1;
+            final int topDiference = mLocation.top - another.mLocation.top;
+            if (topDiference != 0) {
+                return topDiference;
             }
             // LTR
             if (mLayoutDirection == LAYOUT_DIRECTION_LTR) {
@@ -6935,11 +6957,6 @@
                     return -rightDifference;
                 }
             }
-            // Break tie by top.
-            final int topDiference = mLocation.top - another.mLocation.top;
-            if (topDiference != 0) {
-                return topDiference;
-            }
             // Break tie by height.
             final int heightDiference = mLocation.height() - another.mLocation.height();
             if (heightDiference != 0) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index aa06d15..1be0d4e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -181,7 +181,6 @@
     int mWidth;
     int mHeight;
     Rect mDirty;
-    final Rect mCurrentDirty = new Rect();
     boolean mIsAnimating;
 
     CompatibilityInfo.Translator mTranslator;
@@ -230,6 +229,7 @@
     QueuedInputEvent mPendingInputEventTail;
     int mPendingInputEventCount;
     boolean mProcessInputEventsScheduled;
+    boolean mUnbufferedInputDispatch;
     String mPendingInputEventQueueLengthCounterName = "pq";
 
     InputStage mFirstInputStage;
@@ -715,17 +715,6 @@
 
             if (!HardwareRenderer.sRendererDisabled || (HardwareRenderer.sSystemRendererDisabled
                     && forceHwAccelerated)) {
-                if (!HardwareRenderer.sUseRenderThread) {
-                    // TODO: Delete
-                    // Don't enable hardware acceleration when we're not on the main thread
-                    if (!HardwareRenderer.sSystemRendererDisabled &&
-                            Looper.getMainLooper() != Looper.myLooper()) {
-                        Log.w(HardwareRenderer.LOG_TAG, "Attempting to initialize hardware "
-                                + "acceleration outside of the main thread, aborting");
-                        return;
-                    }
-                }
-
                 if (mAttachInfo.mHardwareRenderer != null) {
                     mAttachInfo.mHardwareRenderer.destroy(true);
                 }
@@ -871,7 +860,9 @@
 
     void invalidate() {
         mDirty.set(0, 0, mWidth, mHeight);
-        scheduleTraversals();
+        if (!mWillDrawSoon) {
+            scheduleTraversals();
+        }
     }
 
     void invalidateWorld(View view) {
@@ -1016,7 +1007,9 @@
             mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
             mChoreographer.postCallback(
                     Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
-            scheduleConsumeBatchedInput();
+            if (!mUnbufferedInputDispatch) {
+                scheduleConsumeBatchedInput();
+            }
             notifyRendererOfFramePending();
         }
     }
@@ -2444,12 +2437,10 @@
                 mHardwareYOffset = yoff;
                 mResizeAlpha = resizeAlpha;
 
-                mCurrentDirty.set(dirty);
                 dirty.setEmpty();
 
                 mBlockResizeBuffer = false;
-                attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
-                        animating ? null : mCurrentDirty);
+                attachInfo.mHardwareRenderer.draw(mView, attachInfo, this);
             } else {
                 // If we get here with a disabled & requested hardware renderer, something went
                 // wrong (an invalidate posted right before we destroyed the hardware surface
@@ -2616,7 +2607,7 @@
         }
 
         final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
-        final Rect bounds = mView.mAttachInfo.mTmpInvalRect;
+        final Rect bounds = mAttachInfo.mTmpInvalRect;
         if (provider == null) {
             host.getBoundsOnScreen(bounds);
         } else if (mAccessibilityFocusedVirtualView != null) {
@@ -3898,6 +3889,18 @@
             }
         }
 
+        @Override
+        protected void onDeliverToNext(QueuedInputEvent q) {
+            if (mUnbufferedInputDispatch
+                    && q.mEvent instanceof MotionEvent
+                    && ((MotionEvent)q.mEvent).isTouchEvent()
+                    && isTerminalInputEvent(q.mEvent)) {
+                mUnbufferedInputDispatch = false;
+                scheduleConsumeBatchedInput();
+            }
+            super.onDeliverToNext(q);
+        }
+
         private int processKeyEvent(QueuedInputEvent q) {
             final KeyEvent event = (KeyEvent)q.mEvent;
 
@@ -4010,10 +4013,15 @@
         private int processPointerEvent(QueuedInputEvent q) {
             final MotionEvent event = (MotionEvent)q.mEvent;
 
-            if (mView.dispatchPointerEvent(event)) {
-                return FINISH_HANDLED;
+            mAttachInfo.mUnbufferedDispatchRequested = false;
+            boolean handled = mView.dispatchPointerEvent(event);
+            if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
+                mUnbufferedInputDispatch = true;
+                if (mConsumeBatchedInputScheduled) {
+                    scheduleConsumeBatchedInputImmediately();
+                }
             }
-            return FORWARD;
+            return handled ? FINISH_HANDLED : FORWARD;
         }
 
         private int processTrackballEvent(QueuedInputEvent q) {
@@ -5278,6 +5286,8 @@
                 writer.print(" mRemoved="); writer.println(mRemoved);
         writer.print(innerPrefix); writer.print("mConsumeBatchedInputScheduled=");
                 writer.println(mConsumeBatchedInputScheduled);
+        writer.print(innerPrefix); writer.print("mConsumeBatchedInputImmediatelyScheduled=");
+                writer.println(mConsumeBatchedInputImmediatelyScheduled);
         writer.print(innerPrefix); writer.print("mPendingInputEventCount=");
                 writer.println(mPendingInputEventCount);
         writer.print(innerPrefix); writer.print("mProcessInputEventsScheduled=");
@@ -5688,6 +5698,7 @@
     private void finishInputEvent(QueuedInputEvent q) {
         Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
                 q.mEvent.getSequenceNumber());
+
         if (q.mReceiver != null) {
             boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;
             q.mReceiver.finishInputEvent(q.mEvent, handled);
@@ -5727,15 +5738,25 @@
         }
     }
 
+    void scheduleConsumeBatchedInputImmediately() {
+        if (!mConsumeBatchedInputImmediatelyScheduled) {
+            unscheduleConsumeBatchedInput();
+            mConsumeBatchedInputImmediatelyScheduled = true;
+            mHandler.post(mConsumeBatchedInputImmediatelyRunnable);
+        }
+    }
+
     void doConsumeBatchedInput(long frameTimeNanos) {
         if (mConsumeBatchedInputScheduled) {
             mConsumeBatchedInputScheduled = false;
             if (mInputEventReceiver != null) {
-                if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)) {
+                if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)
+                        && frameTimeNanos != -1) {
                     // If we consumed a batch here, we want to go ahead and schedule the
                     // consumption of batched input events on the next frame. Otherwise, we would
                     // wait until we have more input events pending and might get starved by other
-                    // things occurring in the process.
+                    // things occurring in the process. If the frame time is -1, however, then
+                    // we're in a non-batching mode, so there's no need to schedule this.
                     scheduleConsumeBatchedInput();
                 }
             }
@@ -5763,7 +5784,11 @@
 
         @Override
         public void onBatchedInputEventPending() {
-            scheduleConsumeBatchedInput();
+            if (mUnbufferedInputDispatch) {
+                super.onBatchedInputEventPending();
+            } else {
+                scheduleConsumeBatchedInput();
+            }
         }
 
         @Override
@@ -5784,6 +5809,16 @@
             new ConsumeBatchedInputRunnable();
     boolean mConsumeBatchedInputScheduled;
 
+    final class ConsumeBatchedInputImmediatelyRunnable implements Runnable {
+        @Override
+        public void run() {
+            doConsumeBatchedInput(-1);
+        }
+    }
+    final ConsumeBatchedInputImmediatelyRunnable mConsumeBatchedInputImmediatelyRunnable =
+            new ConsumeBatchedInputImmediatelyRunnable();
+    boolean mConsumeBatchedInputImmediatelyScheduled;
+
     final class InvalidateOnAnimationRunnable implements Runnable {
         private boolean mPosted;
         private final ArrayList<View> mViews = new ArrayList<View>();
@@ -6180,7 +6215,7 @@
             mTempRect.offset(0, -mCurScrollY);
             mTempRect.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
             try {
-                mWindowSession.onRectangleOnScreenRequested(mWindow, mTempRect, immediate);
+                mWindowSession.onRectangleOnScreenRequested(mWindow, mTempRect);
             } catch (RemoteException re) {
                 /* ignore */
             }
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index ecc4586..0120875 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1516,6 +1516,29 @@
     public boolean getAllowExitTransitionOverlap() { return true; }
 
     /**
+     * Returns the duration, in milliseconds, of the window background fade
+     * when transitioning into or away from an Activity when called with an Activity Transition.
+     * <p>When executing the enter transition, the background starts transparent
+     * and fades in. This requires {@link #FEATURE_CONTENT_TRANSITIONS}. The default is
+     * 300 milliseconds.</p>
+     * @return The duration of the window background fade to opaque during enter transition.
+     * @see #getEnterTransition()
+     */
+    public long getTransitionBackgroundFadeDuration() { return 0; }
+
+    /**
+     * Sets the duration, in milliseconds, of the window background fade
+     * when transitioning into or away from an Activity when called with an Activity Transition.
+     * <p>When executing the enter transition, the background starts transparent
+     * and fades in. This requires {@link #FEATURE_CONTENT_TRANSITIONS}. The default is
+     * 300 milliseconds.</p>
+     * @param fadeDurationMillis The duration of the window background fade to or from opaque
+     *                           during enter transition.
+     * @see #setEnterTransition(android.transition.Transition)
+     */
+    public void setTransitionBackgroundFadeDuration(long fadeDurationMillis) { }
+
+    /**
      * @return the color of the status bar.
      */
     public abstract int getStatusBarColor();
diff --git a/core/java/android/view/WindowInfo.java b/core/java/android/view/WindowInfo.java
index 7f89044..b721074 100644
--- a/core/java/android/view/WindowInfo.java
+++ b/core/java/android/view/WindowInfo.java
@@ -111,6 +111,7 @@
         builder.append("type=").append(type);
         builder.append(", layer=").append(layer);
         builder.append(", token=").append(token);
+        builder.append(", bounds=").append(boundsInScreen);
         builder.append(", parent=").append(parentToken);
         builder.append(", focused=").append(focused);
         builder.append(", children=").append(childTokens);
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 3a1e826..7b3dc84 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -96,7 +96,12 @@
         if (mTempRect == null) {
             mTempRect = new Rect();
         }
-        mTempRect.set(mSystemWindowInsets);
+        if (mSystemWindowInsets != null) {
+            mTempRect.set(mSystemWindowInsets);
+        } else {
+            // If there were no system window insets, this is just empty.
+            mTempRect.setEmpty();
+        }
         return mTempRect;
     }
 
diff --git a/core/java/android/view/animation/AccelerateInterpolator.java b/core/java/android/view/animation/AccelerateInterpolator.java
index c08f348..1c75f16 100644
--- a/core/java/android/view/animation/AccelerateInterpolator.java
+++ b/core/java/android/view/animation/AccelerateInterpolator.java
@@ -17,15 +17,18 @@
 package android.view.animation;
 
 import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
 
+import com.android.internal.R;
 import com.android.internal.view.animation.HasNativeInterpolator;
 import com.android.internal.view.animation.NativeInterpolatorFactory;
 import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
 
 /**
- * An interpolator where the rate of change starts out slowly and 
+ * An interpolator where the rate of change starts out slowly and
  * and then accelerates.
  *
  */
@@ -38,10 +41,10 @@
         mFactor = 1.0f;
         mDoubleFactor = 2.0;
     }
-    
+
     /**
      * Constructor
-     * 
+     *
      * @param factor Degree to which the animation should be eased. Seting
      *        factor to 1.0f produces a y=x^2 parabola. Increasing factor above
      *        1.0f  exaggerates the ease-in effect (i.e., it starts even
@@ -51,17 +54,26 @@
         mFactor = factor;
         mDoubleFactor = 2 * mFactor;
     }
-    
+
     public AccelerateInterpolator(Context context, AttributeSet attrs) {
-        TypedArray a =
-            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AccelerateInterpolator);
-        
-        mFactor = a.getFloat(com.android.internal.R.styleable.AccelerateInterpolator_factor, 1.0f);
+        this(context.getResources(), context.getTheme(), attrs);
+    }
+
+    /** @hide */
+    public AccelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, R.styleable.AccelerateInterpolator, 0, 0);
+        } else {
+            a = res.obtainAttributes(attrs, R.styleable.AccelerateInterpolator);
+        }
+
+        mFactor = a.getFloat(R.styleable.AccelerateInterpolator_factor, 1.0f);
         mDoubleFactor = 2 * mFactor;
 
         a.recycle();
     }
-    
+
     public float getInterpolation(float input) {
         if (mFactor == 1.0f) {
             return input * input;
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 1d1fa1e..af4e04f 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -20,6 +20,8 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
 import android.content.res.XmlResourceParser;
 import android.content.res.Resources.NotFoundException;
 import android.util.AttributeSet;
@@ -143,7 +145,7 @@
      */
     public static LayoutAnimationController loadLayoutAnimation(Context context, int id)
             throws NotFoundException {
-        
+
         XmlResourceParser parser = null;
         try {
             parser = context.getResources().getAnimation(id);
@@ -201,7 +203,7 @@
     /**
      * Make an animation for objects becoming visible. Uses a slide and fade
      * effect.
-     * 
+     *
      * @param c Context for loading resources
      * @param fromLeft is the object to be animated coming from the left
      * @return The new animation
@@ -218,11 +220,11 @@
         a.setStartTime(currentAnimationTimeMillis());
         return a;
     }
-    
+
     /**
      * Make an animation for objects becoming invisible. Uses a slide and fade
      * effect.
-     * 
+     *
      * @param c Context for loading resources
      * @param toRight is the object to be animated exiting to the right
      * @return The new animation
@@ -234,17 +236,17 @@
         } else {
             a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_out_left);
         }
-        
+
         a.setInterpolator(new AccelerateInterpolator());
         a.setStartTime(currentAnimationTimeMillis());
         return a;
     }
 
-    
+
     /**
      * Make an animation for objects becoming visible. Uses a slide up and fade
      * effect.
-     * 
+     *
      * @param c Context for loading resources
      * @return The new animation
      */
@@ -255,10 +257,10 @@
         a.setStartTime(currentAnimationTimeMillis());
         return a;
     }
-    
+
     /**
      * Loads an {@link Interpolator} object from a resource
-     * 
+     *
      * @param context Application context used to access resources
      * @param id The resource id of the animation to load
      * @return The animation object reference by the specified id
@@ -268,7 +270,7 @@
         XmlResourceParser parser = null;
         try {
             parser = context.getResources().getAnimation(id);
-            return createInterpolatorFromXml(context, parser);
+            return createInterpolatorFromXml(context.getResources(), context.getTheme(), parser);
         } catch (XmlPullParserException ex) {
             NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
                     Integer.toHexString(id));
@@ -284,54 +286,84 @@
         }
 
     }
-    
-    private static Interpolator createInterpolatorFromXml(Context c, XmlPullParser parser)
+
+    /**
+     * Loads an {@link Interpolator} object from a resource
+     *
+     * @param res The resources
+     * @param id The resource id of the animation to load
+     * @return The interpolator object reference by the specified id
+     * @throws NotFoundException
+     * @hide
+     */
+    public static Interpolator loadInterpolator(Resources res, Theme theme, int id) throws NotFoundException {
+        XmlResourceParser parser = null;
+        try {
+            parser = res.getAnimation(id);
+            return createInterpolatorFromXml(res, theme, parser);
+        } catch (XmlPullParserException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } finally {
+            if (parser != null)
+                parser.close();
+        }
+
+    }
+
+    private static Interpolator createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser)
             throws XmlPullParserException, IOException {
-        
+
         Interpolator interpolator = null;
- 
+
         // Make sure we are on a start tag.
         int type;
         int depth = parser.getDepth();
 
-        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
-               && type != XmlPullParser.END_DOCUMENT) {
+        while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+                && type != XmlPullParser.END_DOCUMENT) {
 
             if (type != XmlPullParser.START_TAG) {
                 continue;
             }
 
             AttributeSet attrs = Xml.asAttributeSet(parser);
-            
-            String  name = parser.getName();
-    
-            
+
+            String name = parser.getName();
+
             if (name.equals("linearInterpolator")) {
-                interpolator = new LinearInterpolator(c, attrs);
+                interpolator = new LinearInterpolator();
             } else if (name.equals("accelerateInterpolator")) {
-                interpolator = new AccelerateInterpolator(c, attrs);
+                interpolator = new AccelerateInterpolator(res, theme, attrs);
             } else if (name.equals("decelerateInterpolator")) {
-                interpolator = new DecelerateInterpolator(c, attrs);
-            }  else if (name.equals("accelerateDecelerateInterpolator")) {
-                interpolator = new AccelerateDecelerateInterpolator(c, attrs);
-            }  else if (name.equals("cycleInterpolator")) {
-                interpolator = new CycleInterpolator(c, attrs);
+                interpolator = new DecelerateInterpolator(res, theme, attrs);
+            } else if (name.equals("accelerateDecelerateInterpolator")) {
+                interpolator = new AccelerateDecelerateInterpolator();
+            } else if (name.equals("cycleInterpolator")) {
+                interpolator = new CycleInterpolator(res, theme, attrs);
             } else if (name.equals("anticipateInterpolator")) {
-                interpolator = new AnticipateInterpolator(c, attrs);
+                interpolator = new AnticipateInterpolator(res, theme, attrs);
             } else if (name.equals("overshootInterpolator")) {
-                interpolator = new OvershootInterpolator(c, attrs);
+                interpolator = new OvershootInterpolator(res, theme, attrs);
             } else if (name.equals("anticipateOvershootInterpolator")) {
-                interpolator = new AnticipateOvershootInterpolator(c, attrs);
+                interpolator = new AnticipateOvershootInterpolator(res, theme, attrs);
             } else if (name.equals("bounceInterpolator")) {
-                interpolator = new BounceInterpolator(c, attrs);
+                interpolator = new BounceInterpolator();
             } else if (name.equals("pathInterpolator")) {
-                interpolator = new PathInterpolator(c, attrs);
+                interpolator = new PathInterpolator(res, theme, attrs);
             } else {
                 throw new RuntimeException("Unknown interpolator name: " + parser.getName());
             }
 
         }
-    
+
         return interpolator;
 
     }
diff --git a/core/java/android/view/animation/AnticipateInterpolator.java b/core/java/android/view/animation/AnticipateInterpolator.java
index 83a8007..fe756bd 100644
--- a/core/java/android/view/animation/AnticipateInterpolator.java
+++ b/core/java/android/view/animation/AnticipateInterpolator.java
@@ -17,9 +17,12 @@
 package android.view.animation;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.content.res.Resources.Theme;
 import android.util.AttributeSet;
 
+import com.android.internal.R;
 import com.android.internal.view.animation.HasNativeInterpolator;
 import com.android.internal.view.animation.NativeInterpolatorFactory;
 import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
@@ -45,11 +48,20 @@
     }
 
     public AnticipateInterpolator(Context context, AttributeSet attrs) {
-        TypedArray a = context.obtainStyledAttributes(attrs,
-                com.android.internal.R.styleable.AnticipateInterpolator);
+        this(context.getResources(), context.getTheme(), attrs);
+    }
+
+    /** @hide */
+    public AnticipateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, R.styleable.AnticipateInterpolator, 0, 0);
+        } else {
+            a = res.obtainAttributes(attrs, R.styleable.AnticipateInterpolator);
+        }
 
         mTension =
-                a.getFloat(com.android.internal.R.styleable.AnticipateInterpolator_tension, 2.0f);
+                a.getFloat(R.styleable.AnticipateInterpolator_tension, 2.0f);
 
         a.recycle();
     }
diff --git a/core/java/android/view/animation/AnticipateOvershootInterpolator.java b/core/java/android/view/animation/AnticipateOvershootInterpolator.java
index 1a8adfd..78e5acf 100644
--- a/core/java/android/view/animation/AnticipateOvershootInterpolator.java
+++ b/core/java/android/view/animation/AnticipateOvershootInterpolator.java
@@ -17,6 +17,8 @@
 package android.view.animation;
 
 import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
 
@@ -62,7 +64,17 @@
     }
 
     public AnticipateOvershootInterpolator(Context context, AttributeSet attrs) {
-        TypedArray a = context.obtainStyledAttributes(attrs, AnticipateOvershootInterpolator);
+        this(context.getResources(), context.getTheme(), attrs);
+    }
+
+    /** @hide */
+    public AnticipateOvershootInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, AnticipateOvershootInterpolator, 0, 0);
+        } else {
+            a = res.obtainAttributes(attrs, AnticipateOvershootInterpolator);
+        }
 
         mTension = a.getFloat(AnticipateOvershootInterpolator_tension, 2.0f) *
                 a.getFloat(AnticipateOvershootInterpolator_extraTension, 1.5f);
diff --git a/core/java/android/view/animation/CycleInterpolator.java b/core/java/android/view/animation/CycleInterpolator.java
index d1ebf05..3114aa3 100644
--- a/core/java/android/view/animation/CycleInterpolator.java
+++ b/core/java/android/view/animation/CycleInterpolator.java
@@ -17,9 +17,12 @@
 package android.view.animation;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.content.res.Resources.Theme;
 import android.util.AttributeSet;
 
+import com.android.internal.R;
 import com.android.internal.view.animation.HasNativeInterpolator;
 import com.android.internal.view.animation.NativeInterpolatorFactory;
 import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
@@ -34,20 +37,29 @@
     public CycleInterpolator(float cycles) {
         mCycles = cycles;
     }
-    
+
     public CycleInterpolator(Context context, AttributeSet attrs) {
-        TypedArray a =
-            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.CycleInterpolator);
-        
-        mCycles = a.getFloat(com.android.internal.R.styleable.CycleInterpolator_cycles, 1.0f);
-        
+        this(context.getResources(), context.getTheme(), attrs);
+    }
+
+    /** @hide */
+    public CycleInterpolator(Resources resources, Theme theme, AttributeSet attrs) {
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, R.styleable.CycleInterpolator, 0, 0);
+        } else {
+            a = resources.obtainAttributes(attrs, R.styleable.CycleInterpolator);
+        }
+
+        mCycles = a.getFloat(R.styleable.CycleInterpolator_cycles, 1.0f);
+
         a.recycle();
     }
-    
+
     public float getInterpolation(float input) {
         return (float)(Math.sin(2 * mCycles * Math.PI * input));
     }
-    
+
     private float mCycles;
 
     /** @hide */
diff --git a/core/java/android/view/animation/DecelerateInterpolator.java b/core/java/android/view/animation/DecelerateInterpolator.java
index 0789a0e..674207c 100644
--- a/core/java/android/view/animation/DecelerateInterpolator.java
+++ b/core/java/android/view/animation/DecelerateInterpolator.java
@@ -17,15 +17,18 @@
 package android.view.animation;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.content.res.Resources.Theme;
 import android.util.AttributeSet;
 
+import com.android.internal.R;
 import com.android.internal.view.animation.HasNativeInterpolator;
 import com.android.internal.view.animation.NativeInterpolatorFactory;
 import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
 
 /**
- * An interpolator where the rate of change starts out quickly and 
+ * An interpolator where the rate of change starts out quickly and
  * and then decelerates.
  *
  */
@@ -36,7 +39,7 @@
 
     /**
      * Constructor
-     * 
+     *
      * @param factor Degree to which the animation should be eased. Setting factor to 1.0f produces
      *        an upside-down y=x^2 parabola. Increasing factor above 1.0f makes exaggerates the
      *        ease-out effect (i.e., it starts even faster and ends evens slower)
@@ -44,16 +47,25 @@
     public DecelerateInterpolator(float factor) {
         mFactor = factor;
     }
-    
+
     public DecelerateInterpolator(Context context, AttributeSet attrs) {
-        TypedArray a =
-            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.DecelerateInterpolator);
-        
-        mFactor = a.getFloat(com.android.internal.R.styleable.DecelerateInterpolator_factor, 1.0f);
-        
+        this(context.getResources(), context.getTheme(), attrs);
+    }
+
+    /** @hide */
+    public DecelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, R.styleable.DecelerateInterpolator, 0, 0);
+        } else {
+            a = res.obtainAttributes(attrs, R.styleable.DecelerateInterpolator);
+        }
+
+        mFactor = a.getFloat(R.styleable.DecelerateInterpolator_factor, 1.0f);
+
         a.recycle();
     }
-    
+
     public float getInterpolation(float input) {
         float result;
         if (mFactor == 1.0f) {
@@ -63,7 +75,7 @@
         }
         return result;
     }
-    
+
     private float mFactor = 1.0f;
 
     /** @hide */
diff --git a/core/java/android/view/animation/OvershootInterpolator.java b/core/java/android/view/animation/OvershootInterpolator.java
index a2466f1..d6c2808 100644
--- a/core/java/android/view/animation/OvershootInterpolator.java
+++ b/core/java/android/view/animation/OvershootInterpolator.java
@@ -17,9 +17,12 @@
 package android.view.animation;
 
 import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
 
+import com.android.internal.R;
 import com.android.internal.view.animation.HasNativeInterpolator;
 import com.android.internal.view.animation.NativeInterpolatorFactory;
 import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
@@ -46,11 +49,20 @@
     }
 
     public OvershootInterpolator(Context context, AttributeSet attrs) {
-        TypedArray a = context.obtainStyledAttributes(attrs,
-                com.android.internal.R.styleable.OvershootInterpolator);
+        this(context.getResources(), context.getTheme(), attrs);
+    }
+
+    /** @hide */
+    public OvershootInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, R.styleable.OvershootInterpolator, 0, 0);
+        } else {
+            a = res.obtainAttributes(attrs, R.styleable.OvershootInterpolator);
+        }
 
         mTension =
-                a.getFloat(com.android.internal.R.styleable.OvershootInterpolator_tension, 2.0f);
+                a.getFloat(R.styleable.OvershootInterpolator_tension, 2.0f);
 
         a.recycle();
     }
diff --git a/core/java/android/view/animation/PathInterpolator.java b/core/java/android/view/animation/PathInterpolator.java
index a369509..da12ffb 100644
--- a/core/java/android/view/animation/PathInterpolator.java
+++ b/core/java/android/view/animation/PathInterpolator.java
@@ -16,11 +16,15 @@
 package android.view.animation;
 
 import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.graphics.Path;
 import android.util.AttributeSet;
 import android.view.InflateException;
 
+import com.android.internal.R;
+
 /**
  * An interpolator that can traverse a Path that extends from <code>Point</code>
  * <code>(0, 0)</code> to <code>(1, 1)</code>. The x coordinate along the <code>Path</code>
@@ -81,18 +85,33 @@
     }
 
     public PathInterpolator(Context context, AttributeSet attrs) {
-        TypedArray a = context.obtainStyledAttributes(attrs,
-                com.android.internal.R.styleable.PathInterpolator);
-        if (!a.hasValue(com.android.internal.R.styleable.PathInterpolator_controlX1)) {
+        this(context.getResources(), context.getTheme(), attrs);
+    }
+
+    /** @hide */
+    public PathInterpolator(Resources res, Theme theme, AttributeSet attrs) {
+        TypedArray a;
+        if (theme != null) {
+            a = theme.obtainStyledAttributes(attrs, R.styleable.PathInterpolator, 0, 0);
+        } else {
+            a = res.obtainAttributes(attrs, R.styleable.PathInterpolator);
+        }
+        parseInterpolatorFromTypeArray(a);
+
+        a.recycle();
+    }
+
+    private void parseInterpolatorFromTypeArray(TypedArray a) {
+        if (!a.hasValue(R.styleable.PathInterpolator_controlX1)) {
             throw new InflateException("pathInterpolator requires the controlX1 attribute");
-        } else if (!a.hasValue(com.android.internal.R.styleable.PathInterpolator_controlY1)) {
+        } else if (!a.hasValue(R.styleable.PathInterpolator_controlY1)) {
             throw new InflateException("pathInterpolator requires the controlY1 attribute");
         }
-        float x1 = a.getFloat(com.android.internal.R.styleable.PathInterpolator_controlX1, 0);
-        float y1 = a.getFloat(com.android.internal.R.styleable.PathInterpolator_controlY1, 0);
+        float x1 = a.getFloat(R.styleable.PathInterpolator_controlX1, 0);
+        float y1 = a.getFloat(R.styleable.PathInterpolator_controlY1, 0);
 
-        boolean hasX2 = a.hasValue(com.android.internal.R.styleable.PathInterpolator_controlX2);
-        boolean hasY2 = a.hasValue(com.android.internal.R.styleable.PathInterpolator_controlY2);
+        boolean hasX2 = a.hasValue(R.styleable.PathInterpolator_controlX2);
+        boolean hasY2 = a.hasValue(R.styleable.PathInterpolator_controlY2);
 
         if (hasX2 != hasY2) {
             throw new InflateException(
@@ -102,12 +121,10 @@
         if (!hasX2) {
             initQuad(x1, y1);
         } else {
-            float x2 = a.getFloat(com.android.internal.R.styleable.PathInterpolator_controlX2, 0);
-            float y2 = a.getFloat(com.android.internal.R.styleable.PathInterpolator_controlY2, 0);
+            float x2 = a.getFloat(R.styleable.PathInterpolator_controlX2, 0);
+            float y2 = a.getFloat(R.styleable.PathInterpolator_controlY2, 0);
             initCubic(x1, y1, x2, y2);
         }
-
-        a.recycle();
     }
 
     private void initQuad(float controlX, float controlY) {
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index cccfa78..e1f40b7 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -195,6 +195,7 @@
     public boolean commitText(CharSequence text, int newCursorPosition) {
         if (DEBUG) Log.v(TAG, "commitText " + text);
         replaceText(text, newCursorPosition, false);
+        mIMM.notifyUserAction();
         sendCurrentText();
         return true;
     }
@@ -435,6 +436,7 @@
     public boolean setComposingText(CharSequence text, int newCursorPosition) {
         if (DEBUG) Log.v(TAG, "setComposingText " + text);
         replaceText(text, newCursorPosition, true);
+        mIMM.notifyUserAction();
         return true;
     }
 
@@ -518,6 +520,7 @@
                 viewRootImpl.dispatchKeyFromIme(event);
             }
         }
+        mIMM.notifyUserAction();
         return false;
     }
     
@@ -601,10 +604,6 @@
         }
         
         beginBatchEdit();
-        if (!composing && !TextUtils.isEmpty(text)) {
-            // Notify the text is committed by the user to InputMethodManagerService
-            mIMM.notifyTextCommitted();
-        }
 
         // delete composing text set previously.
         int a = getComposingSpanStart(content);
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index f874eb7..ace8808 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -320,6 +320,25 @@
     int mCursorCandEnd;
 
     /**
+     * Represents an invalid action notification sequence number. {@link InputMethodManagerService}
+     * always issues a positive integer for action notification sequence numbers. Thus -1 is
+     * guaranteed to be different from any valid sequence number.
+     */
+    private static final int NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER = -1;
+    /**
+     * The next sequence number that is to be sent to {@link InputMethodManagerService} via
+     * {@link IInputMethodManager#notifyUserAction(int)} at once when a user action is observed.
+     */
+    private int mNextUserActionNotificationSequenceNumber =
+            NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER;
+
+    /**
+     * The last sequence number that is already sent to {@link InputMethodManagerService}.
+     */
+    private int mLastSentUserActionNotificationSequenceNumber =
+            NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER;
+
+    /**
      * The instance that has previously been sent to the input method.
      */
     private CursorAnchorInfo mCursorAnchorInfo = null;
@@ -363,7 +382,8 @@
     static final int MSG_SEND_INPUT_EVENT = 5;
     static final int MSG_TIMEOUT_INPUT_EVENT = 6;
     static final int MSG_FLUSH_INPUT_EVENT = 7;
-    static final int SET_CURSOR_ANCHOR_MONITOR_MODE = 8;
+    static final int MSG_SET_CURSOR_ANCHOR_MONITOR_MODE = 8;
+    static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 9;
 
     class H extends Handler {
         H(Looper looper) {
@@ -494,7 +514,7 @@
                     finishedInputEvent(msg.arg1, false, false);
                     return;
                 }
-                case SET_CURSOR_ANCHOR_MONITOR_MODE: {
+                case MSG_SET_CURSOR_ANCHOR_MONITOR_MODE: {
                     synchronized (mH) {
                         mCursorAnchorMonitorMode = msg.arg1;
                         // Clear the cache.
@@ -503,6 +523,11 @@
                     }
                     return;
                 }
+                case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: {
+                    synchronized (mH) {
+                        mNextUserActionNotificationSequenceNumber = msg.arg1;
+                    }
+                }
             }
         }
     }
@@ -570,7 +595,13 @@
 
         @Override
         public void setCursorAnchorMonitorMode(int monitorMode) {
-            mH.sendMessage(mH.obtainMessage(SET_CURSOR_ANCHOR_MONITOR_MODE, monitorMode, 0));
+            mH.sendMessage(mH.obtainMessage(MSG_SET_CURSOR_ANCHOR_MONITOR_MODE, monitorMode, 0));
+        }
+
+        @Override
+        public void setUserActionNotificationSequenceNumber(int sequenceNumber) {
+            mH.sendMessage(mH.obtainMessage(MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER,
+                    sequenceNumber, 0));
         }
     };
 
@@ -1214,6 +1245,8 @@
                         mBindSequence = res.sequence;
                         mCurMethod = res.method;
                         mCurId = res.id;
+                        mNextUserActionNotificationSequenceNumber =
+                                res.userActionNotificationSequenceNumber;
                     } else {
                         if (res.channel != null && res.channel != mCurChannel) {
                             res.channel.dispose();
@@ -1913,13 +1946,33 @@
     }
 
     /**
-     * Notify the current IME commits text
+     * Notify that a user took some action with this input method.
      * @hide
      */
-    public void notifyTextCommitted() {
+    public void notifyUserAction() {
         synchronized (mH) {
+            if (mLastSentUserActionNotificationSequenceNumber ==
+                    mNextUserActionNotificationSequenceNumber) {
+                if (DEBUG) {
+                    Log.w(TAG, "Ignoring notifyUserAction as it has already been sent."
+                            + " mLastSentUserActionNotificationSequenceNumber: "
+                            + mLastSentUserActionNotificationSequenceNumber
+                            + " mNextUserActionNotificationSequenceNumber: "
+                            + mNextUserActionNotificationSequenceNumber);
+                }
+                return;
+            }
             try {
-                mService.notifyTextCommitted();
+                if (DEBUG) {
+                    Log.w(TAG, "notifyUserAction: "
+                            + " mLastSentUserActionNotificationSequenceNumber: "
+                            + mLastSentUserActionNotificationSequenceNumber
+                            + " mNextUserActionNotificationSequenceNumber: "
+                            + mNextUserActionNotificationSequenceNumber);
+                }
+                mService.notifyUserAction(mNextUserActionNotificationSequenceNumber);
+                mLastSentUserActionNotificationSequenceNumber =
+                        mNextUserActionNotificationSequenceNumber;
             } catch (RemoteException e) {
                 Log.w(TAG, "IME died: " + mCurId, e);
             }
@@ -2103,6 +2156,10 @@
                 + " mCursorSelEnd=" + mCursorSelEnd
                 + " mCursorCandStart=" + mCursorCandStart
                 + " mCursorCandEnd=" + mCursorCandEnd);
+        p.println("  mNextUserActionNotificationSequenceNumber="
+                + mNextUserActionNotificationSequenceNumber
+                + " mLastSentUserActionNotificationSequenceNumber="
+                + mLastSentUserActionNotificationSequenceNumber);
     }
 
     /**
diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java
index 628da3c..84f395a 100644
--- a/core/java/android/view/textservice/SpellCheckerSession.java
+++ b/core/java/android/view/textservice/SpellCheckerSession.java
@@ -427,8 +427,12 @@
 
         @Override
         public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
-            mHandler.sendMessage(
-                    Message.obtain(mHandler, MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results));
+            synchronized (this) {
+                if (mHandler != null) {
+                    mHandler.sendMessage(Message.obtain(mHandler,
+                            MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results));
+                }
+            }
         }
     }
 
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index 2b75d83..abed082 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -70,8 +70,7 @@
      /**
      * Sets a cookie for the given URL. Any existing cookie with the same host,
      * path and name will be replaced with the new cookie. The cookie being set
-     * must not have expired and must not be a session cookie, otherwise it
-     * will be ignored.
+     * will be ignored if it is expired.
      *
      * @param url the URL for which the cookie is set
      * @param value the cookie as a string, using the format of the 'Set-Cookie'
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index d630a9a..ec396aa 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -92,7 +92,7 @@
     @Deprecated
     public void onShowCustomView(View view, int requestedOrientation,
             CustomViewCallback callback) {};
-    
+
     /**
      * Notify the host application that the current page would
      * like to hide its custom view.
@@ -392,6 +392,79 @@
     }
 
     /**
+     * Tell the client to show a file chooser.
+     *
+     * This is called to handle HTML forms with 'file' input type, in response to the
+     * user pressing the "Select File" button.
+     * To cancel the request, call <code>filePathCallback.onReceiveValue(null)</code> and
+     * return true.
+     *
+     * @param webView The WebView instance that is initiating the request.
+     * @param filePathCallback Invoke this callback to supply the list of paths to files to upload,
+     *                         or NULL to cancel. Must only be called if the
+     *                         <code>showFileChooser</code> implementations returns true.
+     * @param fileChooserParams Describes the mode of file chooser to be opened, and options to be
+     *                          used with it.
+     * @return true if filePathCallback will be invoked, false to use default handling.
+     *
+     * @see FileChooserParams
+     * @hide For API approval
+     */
+    public boolean showFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
+            FileChooserParams fileChooserParams) {
+        return false;
+    }
+
+    /**
+     * Parameters used in the {@link #showFileChooser(WebView,ValueCallback<String[]>,FileChooserParams)}
+     * method.
+     *
+     * This is intended to be used as a read-only data struct by the application.
+     * @hide For API approval
+     */
+    public static class FileChooserParams {
+        // Flags for mode
+        /** Bitflag for <code>mode</code> indicating multiple files maybe selected */
+        public static final int MODE_OPEN_MULTIPLE = 1 << 0;
+        /** Bitflag for <code>mode</code> indicating a folder  maybe selected.
+         * The implementation should enumerate all files selected by this operation */
+        public static final int MODE_OPEN_FOLDER = 1 << 1;
+        /** Bitflag for <code>mode</code> indicating a non-existant filename maybe returned */
+        public static final int MODE_SAVE = 1 << 2;
+
+        /**
+         * Bit-field of the <code>MODE_</code> flags.
+         *
+         * 0 indicates plain single file open.
+         */
+        public int mode;
+
+        /**
+         * Comma-seperated list of acceptable MIME types.
+         */
+        public String acceptTypes;
+
+        /**
+         * true indicates a preference for a live media captured value (e.g. Camera, Microphone).
+         *
+         * Use <code>acceptTypes</code> to determine suitable capture devices.
+         */
+        public boolean capture;
+
+        /**
+         * The title to use for this file selector, or null.
+         *
+         * Maybe null, in which case a default title should be used.
+         */
+        public String title;
+
+        /**
+         * Name of a default selection if appropriate, or null.
+         */
+        public String defaultFilename;
+    };
+
+    /**
      * Tell the client to open a file chooser.
      * @param uploadFile A ValueCallback to set the URI of the file to upload.
      *      onReceiveValue must be called to wake up the thread.a
@@ -399,8 +472,11 @@
      *         associated with this file picker.
      * @param capture The value of the 'capture' attribute of the input tag
      *         associated with this file picker.
-     * @hide
+     *
+     * @deprecated Use {@link #showFileChooser} instead.
+     * @hide This method was not published in any SDK version.
      */
+    @Deprecated
     public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) {
         uploadFile.onReceiveValue(null);
     }
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 7c32c5b..d14c19b 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1460,4 +1460,36 @@
      *     {@link #MIXED_CONTENT_NEVER_ALLOW} or {@link #MIXED_CONTENT_COMPATIBILITY_MODE}.
      */
     public abstract int getMixedContentMode();
+
+    /**
+     * Sets whether to use a video overlay for embedded encrypted video.
+     * In API levels prior to {@link android.os.Build.VERSION_CODES#L}, encrypted video can
+     * only be rendered directly on a secure video surface, so it had been a hard problem to play
+     * encrypted video in HTML.  When this flag is on, WebView can play encrypted video (MSE/EME)
+     * by using a video overlay (aka hole-punching) for videos embedded using HTML &lt;video&gt;
+     * tag.<br>
+     * Caution: This setting is intended for use only in a narrow set of circumstances and apps
+     * should only enable it if they require playback of encrypted video content. It will impose
+     * the following limitations on the WebView:
+     * <ul>
+     * <li> Only one video overlay can be played at a time.
+     * <li> Changes made to position or dimensions of a video element may be propagated to the
+     * corresponding video overlay with a noticeable delay.
+     * <li> The video overlay is not visible to web APIs and as such may not interact with
+     * script or styling. For example, CSS styles applied to the &lt;video&gt; tag may be ignored.
+     * </ul>
+     * This is not an exhaustive set of constraints and it may vary with new versions of the
+     * WebView.
+     * @hide
+     */
+    public abstract void setVideoOverlayForEmbeddedEncryptedVideoEnabled(boolean flag);
+
+    /**
+     * Gets whether a video overlay will be used for embedded encrypted video.
+     *
+     * @return true if WebView uses a video overlay for embedded encrypted video.
+     * @see #setVideoOverlayForEmbeddedEncryptedVideoEnabled
+     * @hide
+     */
+    public abstract boolean getVideoOverlayForEmbeddedEncryptedVideoEnabled();
 }
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 945e0e3..6e6a987 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -37,13 +37,6 @@
         String findAddress(String addr);
 
         /**
-         * Implements the API methods:
-         * {@link android.webkit.WebView#enablePlatformNotifications()}
-         * {@link android.webkit.WebView#disablePlatformNotifications()}
-         */
-        void setPlatformNotificationsEnabled(boolean enable);
-
-        /**
          * Implements the API method:
          * {@link android.webkit.WebSettings#getDefaultUserAgent(Context) }
          */
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 265dbcd..2c1a77c 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -24,6 +24,7 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.text.InputType;
+import android.text.format.DateFormat;
 import android.text.format.DateUtils;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -814,8 +815,7 @@
             mSpinners.removeAllViews();
             // We use numeric spinners for year and day, but textual months. Ask icu4c what
             // order the user's locale uses for that combination. http://b/7207103.
-            String pattern = ICU.getBestDateTimePattern("yyyyMMMdd",
-                    Locale.getDefault().toString());
+            String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd");
             char[] order = ICU.getDateFormatOrder(pattern);
             final int spinnerCount = order.length;
             for (int i = 0; i < spinnerCount; i++) {
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 8511601..defc26c 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -104,14 +104,16 @@
  *
  * <h4>Excess Space Distribution</h4>
  *
- * GridLayout's distribution of excess space is based on <em>priority</em>
- * rather than <em>weight</em>.
+ * As of API 21, GridLayout's distribution of excess space accomodates the principle of weight.
+ * In the event that no weights are specified, the previous conventions are respected and
+ * columns and rows are taken as flexible if their views specify some form of alignment
+ * within their groups.
  * <p>
- * A child's ability to stretch is inferred from the alignment properties of
- * its row and column groups (which are typically set by setting the
- * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters).
- * If alignment was defined along a given axis then the component
- * is taken as <em>flexible</em> in that direction. If no alignment was set,
+ * The flexibility of a view is therefore influenced by its alignment which is,
+ * in turn, typically defined by setting the
+ * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters.
+ * If either a weight or alignment were defined along a given axis then the component
+ * is taken as <em>flexible</em> in that direction. If no weight or alignment was set,
  * the component is instead assumed to be <em>inflexible</em>.
  * <p>
  * Multiple components in the same row or column group are
@@ -122,12 +124,16 @@
  * elements is flexible if <em>one</em> of its elements is flexible.
  * <p>
  * To make a column stretch, make sure all of the components inside it define a
- * gravity. To prevent a column from stretching, ensure that one of the components
- * in the column does not define a gravity.
+ * weight or a gravity. To prevent a column from stretching, ensure that one of the components
+ * in the column does not define a weight or a gravity.
  * <p>
  * When the principle of flexibility does not provide complete disambiguation,
  * GridLayout's algorithms favour rows and columns that are closer to its <em>right</em>
- * and <em>bottom</em> edges.
+ * and <em>bottom</em> edges. To be more precise, GridLayout treats each of its layout
+ * parameters as a constraint in the a set of variables that define the grid-lines along a
+ * given axis. During layout, GridLayout solves the constraints so as to return the unique
+ * solution to those constraints for which all variables are less-than-or-equal-to
+ * the corresponding value in any other valid solution.
  *
  * <h4>Interpretation of GONE</h4>
  *
@@ -140,18 +146,6 @@
  * had never been added to it.
  * These statements apply equally to rows as well as columns, and to groups of rows or columns.
  *
- * <h5>Limitations</h5>
- *
- * GridLayout does not provide support for the principle of <em>weight</em>, as defined in
- * {@link LinearLayout.LayoutParams#weight}. In general, it is not therefore possible
- * to configure a GridLayout to distribute excess space between multiple components.
- * <p>
- * Some common use-cases may nevertheless be accommodated as follows.
- * To place equal amounts of space around a component in a cell group;
- * use {@link #CENTER} alignment (or {@link LayoutParams#setGravity(int) gravity}).
- * For complete control over excess space distribution in a row or column;
- * use a {@link LinearLayout} subview to hold the components in the associated cell group.
- * When using either of these techniques, bear in mind that cell groups may be defined to overlap.
  * <p>
  * See {@link GridLayout.LayoutParams} for a full description of the
  * layout parameters used by GridLayout.
@@ -1018,6 +1012,8 @@
             LayoutParams lp = getLayoutParams(c);
             if (firstPass) {
                 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height);
+                mHorizontalAxis.recordOriginalMeasurement(i);
+                mVerticalAxis.recordOriginalMeasurement(i);
             } else {
                 boolean horizontal = (mOrientation == HORIZONTAL);
                 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
@@ -1245,6 +1241,11 @@
         public int[] locations;
         public boolean locationsValid = false;
 
+        public boolean hasWeights;
+        public boolean hasWeightsValid = false;
+        public int[] originalMeasurements;
+        public int[] deltas;
+
         boolean orderPreserved = DEFAULT_ORDER_PRESERVED;
 
         private MutableInt parentMin = new MutableInt(0);
@@ -1321,7 +1322,10 @@
                 // we must include views that are GONE here, see introductory javadoc
                 LayoutParams lp = getLayoutParams(c);
                 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
-                groupBounds.getValue(i).include(GridLayout.this, c, spec, this);
+                int size = (spec.weight == 0) ?
+                        getMeasurementIncludingMargin(c, horizontal) :
+                        getOriginalMeasurements()[i] + getDeltas()[i];
+                groupBounds.getValue(i).include(GridLayout.this, c, spec, this, size);
             }
         }
 
@@ -1693,8 +1697,94 @@
             return trailingMargins;
         }
 
-        private void computeLocations(int[] a) {
+        private void solve(int[] a) {
             solve(getArcs(), a);
+        }
+
+        private boolean computeHasWeights() {
+            for (int i = 0, N = getChildCount(); i < N; i++) {
+                LayoutParams lp = getLayoutParams(getChildAt(i));
+                Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+                if (spec.weight != 0) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private boolean hasWeights() {
+            if (!hasWeightsValid) {
+                hasWeights = computeHasWeights();
+                hasWeightsValid = true;
+            }
+            return hasWeights;
+        }
+
+        public int[] getOriginalMeasurements() {
+            if (originalMeasurements == null) {
+                originalMeasurements = new int[getChildCount()];
+            }
+            return originalMeasurements;
+        }
+
+        private void recordOriginalMeasurement(int i) {
+            if (hasWeights()) {
+                getOriginalMeasurements()[i] = getMeasurementIncludingMargin(getChildAt(i), horizontal);
+            }
+        }
+
+        public int[] getDeltas() {
+            if (deltas == null) {
+                deltas = new int[getChildCount()];
+            }
+            return deltas;
+        }
+
+        private void shareOutDelta() {
+            int totalDelta = 0;
+            float totalWeight = 0;
+            for (int i = 0, N = getChildCount(); i < N; i++) {
+                View c = getChildAt(i);
+                LayoutParams lp = getLayoutParams(c);
+                Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+                float weight = spec.weight;
+                if (weight != 0) {
+                    int delta = getMeasurement(c, horizontal) - getOriginalMeasurements()[i];
+                    totalDelta += delta;
+                    totalWeight += weight;
+                }
+            }
+            for (int i = 0, N = getChildCount(); i < N; i++) {
+                LayoutParams lp = getLayoutParams(getChildAt(i));
+                Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+                float weight = spec.weight;
+                if (weight != 0) {
+                    int delta = Math.round((weight * totalDelta / totalWeight));
+                    deltas[i] = delta;
+                    // the two adjustments below are to counter the above rounding and avoid off-by-ones at the end
+                    totalDelta -= delta;
+                    totalWeight -= weight;
+                }
+            }
+        }
+
+        private void solveAndDistributeSpace(int[] a) {
+            Arrays.fill(getDeltas(), 0);
+            solve(a);
+            shareOutDelta();
+            arcsValid = false;
+            forwardLinksValid = false;
+            backwardLinksValid = false;
+            groupBoundsValid = false;
+            solve(a);
+        }
+
+        private void computeLocations(int[] a) {
+            if (!hasWeights()) {
+                solve(a);
+            } else {
+                solveAndDistributeSpace(a);
+            }
             if (!orderPreserved) {
                 // Solve returns the smallest solution to the constraint system for which all
                 // values are positive. One value is therefore zero - though if the row/col
@@ -1777,6 +1867,10 @@
 
             locations = null;
 
+            originalMeasurements = null;
+            deltas = null;
+            hasWeightsValid = false;
+
             invalidateValues();
         }
 
@@ -1810,6 +1904,9 @@
      * both aspects of alignment within the cell group. It is also possible to specify a child's
      * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)}
      * method.
+     * <p>
+     * The weight property is also included in Spec and specifies the proportion of any
+     * excess space that is due to the associated view.
      *
      * <h4>WRAP_CONTENT and MATCH_PARENT</h4>
      *
@@ -1851,9 +1948,11 @@
      *     <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li>
      *     <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li>
      *     <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li>
+     *     <li>{@link #rowSpec}<code>.weight</code> = 0 </li>
      *     <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li>
      *     <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li>
      *     <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li>
+     *     <li>{@link #columnSpec}<code>.weight</code> = 0 </li>
      * </ul>
      *
      * See {@link GridLayout} for a more complete description of the conventions
@@ -1861,8 +1960,10 @@
      *
      * @attr ref android.R.styleable#GridLayout_Layout_layout_row
      * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan
+     * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight
      * @attr ref android.R.styleable#GridLayout_Layout_layout_column
      * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan
+     * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight
      * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
      */
     public static class LayoutParams extends MarginLayoutParams {
@@ -1889,9 +1990,11 @@
 
         private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column;
         private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan;
+        private static final int COLUMN_WEIGHT = R.styleable.GridLayout_Layout_layout_columnWeight;
 
         private static final int ROW = R.styleable.GridLayout_Layout_layout_row;
         private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan;
+        private static final int ROW_WEIGHT = R.styleable.GridLayout_Layout_layout_rowWeight;
 
         private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity;
 
@@ -2034,11 +2137,13 @@
 
                 int column = a.getInt(COLUMN, DEFAULT_COLUMN);
                 int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE);
-                this.columnSpec = spec(column, colSpan, getAlignment(gravity, true));
+                float colWeight = a.getFloat(COLUMN_WEIGHT, Spec.DEFAULT_WEIGHT);
+                this.columnSpec = spec(column, colSpan, getAlignment(gravity, true), colWeight);
 
                 int row = a.getInt(ROW, DEFAULT_ROW);
                 int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE);
-                this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false));
+                float rowWeight = a.getFloat(ROW_WEIGHT, Spec.DEFAULT_WEIGHT);
+                this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false), rowWeight);
             } finally {
                 a.recycle();
             }
@@ -2273,10 +2378,9 @@
             return before - a.getAlignmentValue(c, size, gl.getLayoutMode());
         }
 
-        protected final void include(GridLayout gl, View c, Spec spec, Axis axis) {
+        protected final void include(GridLayout gl, View c, Spec spec, Axis axis, int size) {
             this.flexibility &= spec.getFlexibility();
             boolean horizontal = axis.horizontal;
-            int size = gl.getMeasurementIncludingMargin(c, horizontal);
             Alignment alignment = gl.getAlignment(spec.alignment, horizontal);
             // todo test this works correctly when the returned value is UNDEFINED
             int before = alignment.getAlignmentValue(c, size, gl.getLayoutMode());
@@ -2401,36 +2505,43 @@
      *   <li>{@link #spec(int, int)}</li>
      *   <li>{@link #spec(int, Alignment)}</li>
      *   <li>{@link #spec(int, int, Alignment)}</li>
+     *   <li>{@link #spec(int, float)}</li>
+     *   <li>{@link #spec(int, int, float)}</li>
+     *   <li>{@link #spec(int, Alignment, float)}</li>
+     *   <li>{@link #spec(int, int, Alignment, float)}</li>
      * </ul>
      *
      */
     public static class Spec {
         static final Spec UNDEFINED = spec(GridLayout.UNDEFINED);
+        static final float DEFAULT_WEIGHT = 0;
 
         final boolean startDefined;
         final Interval span;
         final Alignment alignment;
+        final float weight;
 
-        private Spec(boolean startDefined, Interval span, Alignment alignment) {
+        private Spec(boolean startDefined, Interval span, Alignment alignment, float weight) {
             this.startDefined = startDefined;
             this.span = span;
             this.alignment = alignment;
+            this.weight = weight;
         }
 
-        private Spec(boolean startDefined, int start, int size, Alignment alignment) {
-            this(startDefined, new Interval(start, start + size), alignment);
+        private Spec(boolean startDefined, int start, int size, Alignment alignment, float weight) {
+            this(startDefined, new Interval(start, start + size), alignment, weight);
         }
 
         final Spec copyWriteSpan(Interval span) {
-            return new Spec(startDefined, span, alignment);
+            return new Spec(startDefined, span, alignment, weight);
         }
 
         final Spec copyWriteAlignment(Alignment alignment) {
-            return new Spec(startDefined, span, alignment);
+            return new Spec(startDefined, span, alignment, weight);
         }
 
         final int getFlexibility() {
-            return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH;
+            return (alignment == UNDEFINED_ALIGNMENT && weight == 0) ? INFLEXIBLE : CAN_STRETCH;
         }
 
         /**
@@ -2478,6 +2589,7 @@
      * <ul>
      *     <li> {@code spec.span = [start, start + size]} </li>
      *     <li> {@code spec.alignment = alignment} </li>
+     *     <li> {@code spec.weight = weight} </li>
      * </ul>
      * <p>
      * To leave the start index undefined, use the value {@link #UNDEFINED}.
@@ -2485,9 +2597,55 @@
      * @param start     the start
      * @param size      the size
      * @param alignment the alignment
+     * @param weight    the weight
+     */
+    public static Spec spec(int start, int size, Alignment alignment, float weight) {
+        return new Spec(start != UNDEFINED, start, size, alignment, weight);
+    }
+
+    /**
+     * Equivalent to: {@code spec(start, 1, alignment, weight)}.
+     *
+     * @param start     the start
+     * @param alignment the alignment
+     * @param weight    the weight
+     */
+    public static Spec spec(int start, Alignment alignment, float weight) {
+        return spec(start, 1, alignment, weight);
+    }
+
+    /**
+     * Equivalent to: {@code spec(start, 1, default_alignment, weight)} -
+     * where {@code default_alignment} is specified in
+     * {@link android.widget.GridLayout.LayoutParams}.
+     *
+     * @param start  the start
+     * @param size   the size
+     * @param weight the weight
+     */
+    public static Spec spec(int start, int size, float weight) {
+        return spec(start, size, UNDEFINED_ALIGNMENT, weight);
+    }
+
+    /**
+     * Equivalent to: {@code spec(start, 1, weight)}.
+     *
+     * @param start  the start
+     * @param weight the weight
+     */
+    public static Spec spec(int start, float weight) {
+        return spec(start, 1, weight);
+    }
+
+    /**
+     * Equivalent to: {@code spec(start, size, alignment, 0f)}.
+     *
+     * @param start     the start
+     * @param size      the size
+     * @param alignment the alignment
      */
     public static Spec spec(int start, int size, Alignment alignment) {
-        return new Spec(start != UNDEFINED, start, size, alignment);
+        return spec(start, size, alignment, Spec.DEFAULT_WEIGHT);
     }
 
     /**
diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl
index 03d3b22..77f0dec 100644
--- a/core/java/com/android/internal/app/IMediaContainerService.aidl
+++ b/core/java/com/android/internal/app/IMediaContainerService.aidl
@@ -25,16 +25,18 @@
 interface IMediaContainerService {
     String copyResourceToContainer(in Uri packageURI, String containerId, String key,
             String resFileName, String publicResFileName, boolean isExternal,
-            boolean isForwardLocked);
+            boolean isForwardLocked, in String abiOverride);
     int copyResource(in Uri packageURI, in ContainerEncryptionParams encryptionParams,
             in ParcelFileDescriptor outStream);
-    PackageInfoLite getMinimalPackageInfo(in String packagePath, in int flags, in long threshold);
+    PackageInfoLite getMinimalPackageInfo(in String packagePath, in int flags, in long threshold,
+            in String abiOverride);
     boolean checkInternalFreeStorage(in Uri fileUri, boolean isForwardLocked, in long threshold);
-    boolean checkExternalFreeStorage(in Uri fileUri, boolean isForwardLocked);
+    boolean checkExternalFreeStorage(in Uri fileUri, boolean isForwardLocked, in String abiOverride);
     ObbInfo getObbInfo(in String filename);
     long calculateDirectorySize(in String directory);
     /** Return file system stats: [0] is total bytes, [1] is available bytes */
     long[] getFileSystemStats(in String path);
     void clearDirectory(in String directory);
-    long calculateInstalledSize(in String packagePath, boolean isForwardLocked);
+    long calculateInstalledSize(in String packagePath, boolean isForwardLocked,
+            in String abiOverride);
 }
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 47ef65a..01e5d40 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -35,8 +35,8 @@
 
 
 /*
- * This is used in conjunction with DevicePolicyManager.setForwardingIntents to enable intents to be
- * passed in and out of a managed profile.
+ * This is used in conjunction with the {@link setCrossProfileIntentFilter} method of
+ * {@link DevicePolicyManager} to enable intents to be passed in and out of a managed profile.
  */
 
 public class IntentForwarderActivity extends Activity  {
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 591267e..183dd05 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -484,8 +484,7 @@
 
             mList.clear();
             if (mBaseResolveList != null) {
-                currentResolveList = mBaseResolveList;
-                mOrigResolveList = null;
+                currentResolveList = mOrigResolveList = mBaseResolveList;
             } else {
                 currentResolveList = mOrigResolveList = mPm.queryIntentActivities(
                         mIntent, PackageManager.MATCH_DEFAULT_ONLY
diff --git a/core/java/com/android/internal/backup/BackupConstants.java b/core/java/com/android/internal/backup/BackupConstants.java
deleted file mode 100644
index 4c276b7..0000000
--- a/core/java/com/android/internal/backup/BackupConstants.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2009 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.internal.backup;
-
-/**
- * Constants used internally between the backup manager and its transports
- */
-public class BackupConstants {
-    public static final int TRANSPORT_OK = 0;
-    public static final int TRANSPORT_ERROR = 1;
-    public static final int TRANSPORT_NOT_INITIALIZED = 2;
-    public static final int AGENT_ERROR = 3;
-    public static final int AGENT_UNKNOWN = 4;
-}
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index 1e37fd9..d10451b 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -178,7 +178,7 @@
     /**
      * Get the data for the application returned by {@link #nextRestorePackage}.
      * @param data An open, writable file into which the backup data should be stored.
-     * @return the same error codes as {@link #nextRestorePackage}.
+     * @return the same error codes as {@link #startRestore}.
      */
     int getRestoreData(in ParcelFileDescriptor outFd);
 
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 446ef55..7292116 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -18,6 +18,7 @@
 
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupTransport;
 import android.app.backup.RestoreSet;
 import android.content.ComponentName;
 import android.content.Context;
@@ -47,7 +48,7 @@
  * later restoring from there.  For testing only.
  */
 
-public class LocalTransport extends IBackupTransport.Stub {
+public class LocalTransport extends BackupTransport {
     private static final String TAG = "LocalTransport";
     private static final boolean DEBUG = true;
 
@@ -103,7 +104,7 @@
     public int initializeDevice() {
         if (DEBUG) Log.v(TAG, "wiping all data");
         deleteContents(mCurrentSetDir);
-        return BackupConstants.TRANSPORT_OK;
+        return BackupTransport.TRANSPORT_OK;
     }
 
     public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
@@ -165,7 +166,7 @@
                         entity.write(buf, 0, dataSize);
                     } catch (IOException e) {
                         Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
-                        return BackupConstants.TRANSPORT_ERROR;
+                        return BackupTransport.TRANSPORT_ERROR;
                     } finally {
                         entity.close();
                     }
@@ -173,11 +174,11 @@
                     entityFile.delete();
                 }
             }
-            return BackupConstants.TRANSPORT_OK;
+            return BackupTransport.TRANSPORT_OK;
         } catch (IOException e) {
             // oops, something went wrong.  abort the operation and return error.
             Log.v(TAG, "Exception reading backup input:", e);
-            return BackupConstants.TRANSPORT_ERROR;
+            return BackupTransport.TRANSPORT_ERROR;
         }
     }
 
@@ -207,17 +208,17 @@
             }
             packageDir.delete();
         }
-        return BackupConstants.TRANSPORT_OK;
+        return BackupTransport.TRANSPORT_OK;
     }
 
     public int finishBackup() {
         if (DEBUG) Log.v(TAG, "finishBackup()");
-        return BackupConstants.TRANSPORT_OK;
+        return BackupTransport.TRANSPORT_OK;
     }
 
     // Restore handling
     static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 }; 
-    public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
+    public RestoreSet[] getAvailableRestoreSets() {
         long[] existing = new long[POSSIBLE_SETS.length + 1];
         int num = 0;
 
@@ -248,7 +249,7 @@
         mRestorePackage = -1;
         mRestoreToken = token;
         mRestoreDataDir = new File(mDataDir, Long.toString(token));
-        return BackupConstants.TRANSPORT_OK;
+        return BackupTransport.TRANSPORT_OK;
     }
 
     public String nextRestorePackage() {
@@ -280,7 +281,7 @@
         ArrayList<DecodedFilename> blobs = contentsByKey(packageDir);
         if (blobs == null) {  // nextRestorePackage() ensures the dir exists, so this is an error
             Log.e(TAG, "No keys for package: " + packageDir);
-            return BackupConstants.TRANSPORT_ERROR;
+            return BackupTransport.TRANSPORT_ERROR;
         }
 
         // We expect at least some data if the directory exists in the first place
@@ -301,10 +302,10 @@
                     in.close();
                 }
             }
-            return BackupConstants.TRANSPORT_OK;
+            return BackupTransport.TRANSPORT_OK;
         } catch (IOException e) {
             Log.e(TAG, "Unable to read backup records", e);
-            return BackupConstants.TRANSPORT_ERROR;
+            return BackupTransport.TRANSPORT_ERROR;
         }
     }
 
diff --git a/core/java/com/android/internal/backup/LocalTransportService.java b/core/java/com/android/internal/backup/LocalTransportService.java
index d05699a..77ac313 100644
--- a/core/java/com/android/internal/backup/LocalTransportService.java
+++ b/core/java/com/android/internal/backup/LocalTransportService.java
@@ -32,6 +32,6 @@
 
     @Override
     public IBinder onBind(Intent intent) {
-        return sTransport;
+        return sTransport.getBinder();
     }
 }
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index ba419f9..dab3aff 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -20,6 +20,7 @@
 import android.util.Slog;
 
 import java.io.File;
+import java.io.IOException;
 
 /**
  * Native libraries helper.
@@ -141,4 +142,18 @@
 
         return deletedFiles;
     }
+
+    // We don't care about the other return values for now.
+    private static final int BITCODE_PRESENT = 1;
+
+    public static boolean hasRenderscriptBitcode(ApkHandle handle) throws IOException {
+        final int returnVal = hasRenderscriptBitcode(handle.apkHandle);
+        if (returnVal < 0) {
+            throw new IOException("Error scanning APK, code: " + returnVal);
+        }
+
+        return (returnVal == BITCODE_PRESENT);
+    }
+
+    private static native int hasRenderscriptBitcode(long apkHandle);
 }
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
index 495d5c688..fdd24a6 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.Slog;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
@@ -33,6 +34,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.TreeMap;
 
 /**
@@ -116,6 +118,24 @@
                     + " mIsSystemLanguage=" + mIsSystemLanguage
                     + "}";
         }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (o instanceof ImeSubtypeListItem) {
+                final ImeSubtypeListItem that = (ImeSubtypeListItem)o;
+                if (!Objects.equals(this.mImi, that.mImi)) {
+                    return false;
+                }
+                if (this.mSubtypeId != that.mSubtypeId) {
+                    return false;
+                }
+                return true;
+            }
+            return false;
+        }
     }
 
     private static class InputMethodAndSubtypeList {
@@ -211,54 +231,233 @@
         }
     }
 
-    private final InputMethodSettings mSettings;
-    private InputMethodAndSubtypeList mSubtypeList;
+    private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) {
+        return subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi,
+                subtype.hashCode()) : NOT_A_SUBTYPE_ID;
+    }
 
-    @VisibleForTesting
-    public static ImeSubtypeListItem getNextInputMethodLockedImpl(List<ImeSubtypeListItem> imList,
-            boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) {
-        if (imi == null) {
-            return null;
+    private static class StaticRotationList {
+        private final List<ImeSubtypeListItem> mImeSubtypeList;
+        public StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList) {
+            mImeSubtypeList = imeSubtypeList;
         }
-        if (imList.size() <= 1) {
-            return null;
-        }
-        // Here we have two rotation groups, depending on the returned boolean value of
-        // {@link InputMethodInfo#supportsSwitchingToNextInputMethod()}.
-        final boolean expectedValueOfSupportsSwitchingToNextInputMethod =
-                imi.supportsSwitchingToNextInputMethod();
-        final int N = imList.size();
-        final int currentSubtypeId =
-                subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi,
-                        subtype.hashCode()) : NOT_A_SUBTYPE_ID;
-        for (int i = 0; i < N; ++i) {
-            final ImeSubtypeListItem isli = imList.get(i);
-            // Skip until the current IME/subtype is found.
-            if (!isli.mImi.equals(imi) || isli.mSubtypeId != currentSubtypeId) {
-                continue;
-            }
-            // Found the current IME/subtype. Start searching the next IME/subtype from here.
-            for (int j = 0; j < N - 1; ++j) {
-                final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N);
-                // Skip if the candidate doesn't belong to the expected rotation group.
-                if (expectedValueOfSupportsSwitchingToNextInputMethod !=
-                        candidate.mImi.supportsSwitchingToNextInputMethod()) {
-                    continue;
+
+        /**
+         * Returns the index of the specified input method and subtype in the given list.
+         * @param imi The {@link InputMethodInfo} to be searched.
+         * @param subtype The {@link InputMethodSubtype} to be searched. null if the input method
+         * does not have a subtype.
+         * @return The index in the given list. -1 if not found.
+         */
+        private int getIndex(InputMethodInfo imi, InputMethodSubtype subtype) {
+            final int currentSubtypeId = calculateSubtypeId(imi, subtype);
+            final int N = mImeSubtypeList.size();
+            for (int i = 0; i < N; ++i) {
+                final ImeSubtypeListItem isli = mImeSubtypeList.get(i);
+                // Skip until the current IME/subtype is found.
+                if (imi.equals(isli.mImi) && isli.mSubtypeId == currentSubtypeId) {
+                    return i;
                 }
+            }
+            return -1;
+        }
+
+        public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
+                InputMethodInfo imi, InputMethodSubtype subtype) {
+            if (imi == null) {
+                return null;
+            }
+            if (mImeSubtypeList.size() <= 1) {
+                return null;
+            }
+            final int currentIndex = getIndex(imi, subtype);
+            if (currentIndex < 0) {
+                return null;
+            }
+            final int N = mImeSubtypeList.size();
+            for (int offset = 1; offset < N; ++offset) {
+                // Start searching the next IME/subtype from the next of the current index.
+                final int candidateIndex = (currentIndex + offset) % N;
+                final ImeSubtypeListItem candidate = mImeSubtypeList.get(candidateIndex);
                 // Skip if searching inside the current IME only, but the candidate is not
                 // the current IME.
-                if (onlyCurrentIme && !candidate.mImi.equals(imi)) {
+                if (onlyCurrentIme && !imi.equals(candidate.mImi)) {
                     continue;
                 }
                 return candidate;
             }
-            // No appropriate IME/subtype is found in the list. Give up.
             return null;
         }
-        // The current IME/subtype is not found in the list. Give up.
-        return null;
     }
 
+    private static class DynamicRotationList {
+        private static final String TAG = DynamicRotationList.class.getSimpleName();
+        private final List<ImeSubtypeListItem> mImeSubtypeList;
+        private final int[] mUsageHistoryOfSubtypeListItemIndex;
+
+        private DynamicRotationList(final List<ImeSubtypeListItem> imeSubtypeListItems) {
+            mImeSubtypeList = imeSubtypeListItems;
+            mUsageHistoryOfSubtypeListItemIndex = new int[mImeSubtypeList.size()];
+            final int N = mImeSubtypeList.size();
+            for (int i = 0; i < N; i++) {
+                mUsageHistoryOfSubtypeListItemIndex[i] = i;
+            }
+        }
+
+        /**
+         * Returns the index of the specified object in
+         * {@link #mUsageHistoryOfSubtypeListItemIndex}.
+         * <p>We call the index of {@link #mUsageHistoryOfSubtypeListItemIndex} as "Usage Rank"
+         * so as not to be confused with the index in {@link #mImeSubtypeList}.
+         * @return -1 when the specified item doesn't belong to {@link #mImeSubtypeList} actually.
+         */
+        private int getUsageRank(final InputMethodInfo imi, InputMethodSubtype subtype) {
+            final int currentSubtypeId = calculateSubtypeId(imi, subtype);
+            final int N = mUsageHistoryOfSubtypeListItemIndex.length;
+            for (int usageRank = 0; usageRank < N; usageRank++) {
+                final int subtypeListItemIndex = mUsageHistoryOfSubtypeListItemIndex[usageRank];
+                final ImeSubtypeListItem subtypeListItem =
+                        mImeSubtypeList.get(subtypeListItemIndex);
+                if (subtypeListItem.mImi.equals(imi) &&
+                        subtypeListItem.mSubtypeId == currentSubtypeId) {
+                    return usageRank;
+                }
+            }
+            // Not found in the known IME/Subtype list.
+            return -1;
+        }
+
+        public void onUserAction(InputMethodInfo imi, InputMethodSubtype subtype) {
+            final int currentUsageRank = getUsageRank(imi, subtype);
+            // Do nothing if currentUsageRank == -1 (not found), or currentUsageRank == 0
+            if (currentUsageRank <= 0) {
+                return;
+            }
+            final int currentItemIndex = mUsageHistoryOfSubtypeListItemIndex[currentUsageRank];
+            System.arraycopy(mUsageHistoryOfSubtypeListItemIndex, 0,
+                    mUsageHistoryOfSubtypeListItemIndex, 1, currentUsageRank);
+            mUsageHistoryOfSubtypeListItemIndex[0] = currentItemIndex;
+        }
+
+        public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
+                InputMethodInfo imi, InputMethodSubtype subtype) {
+            int currentUsageRank = getUsageRank(imi, subtype);
+            if (currentUsageRank < 0) {
+                if (DEBUG) {
+                    Slog.d(TAG, "IME/subtype is not found: " + imi.getId() + ", " + subtype);
+                }
+                return null;
+            }
+            final int N = mUsageHistoryOfSubtypeListItemIndex.length;
+            for (int i = 1; i < N; i++) {
+                final int subtypeListItemRank = (currentUsageRank + i) % N;
+                final int subtypeListItemIndex =
+                        mUsageHistoryOfSubtypeListItemIndex[subtypeListItemRank];
+                final ImeSubtypeListItem subtypeListItem =
+                        mImeSubtypeList.get(subtypeListItemIndex);
+                if (onlyCurrentIme && !imi.equals(subtypeListItem.mImi)) {
+                    continue;
+                }
+                return subtypeListItem;
+            }
+            return null;
+        }
+    }
+
+    @VisibleForTesting
+    public static class ControllerImpl {
+        private final DynamicRotationList mSwitchingAwareRotationList;
+        private final StaticRotationList mSwitchingUnawareRotationList;
+
+        public static ControllerImpl createFrom(final ControllerImpl currentInstance,
+                final List<ImeSubtypeListItem> sortedEnabledItems) {
+            DynamicRotationList switchingAwareRotationList = null;
+            {
+                final List<ImeSubtypeListItem> switchingAwareImeSubtypes =
+                        filterImeSubtypeList(sortedEnabledItems,
+                                true /* supportsSwitchingToNextInputMethod */);
+                if (currentInstance != null &&
+                        currentInstance.mSwitchingAwareRotationList != null &&
+                        Objects.equals(currentInstance.mSwitchingAwareRotationList.mImeSubtypeList,
+                                switchingAwareImeSubtypes)) {
+                    // Can reuse the current instance.
+                    switchingAwareRotationList = currentInstance.mSwitchingAwareRotationList;
+                }
+                if (switchingAwareRotationList == null) {
+                    switchingAwareRotationList = new DynamicRotationList(switchingAwareImeSubtypes);
+                }
+            }
+
+            StaticRotationList switchingUnawareRotationList = null;
+            {
+                final List<ImeSubtypeListItem> switchingUnawareImeSubtypes = filterImeSubtypeList(
+                        sortedEnabledItems, false /* supportsSwitchingToNextInputMethod */);
+                if (currentInstance != null &&
+                        currentInstance.mSwitchingUnawareRotationList != null &&
+                        Objects.equals(
+                                currentInstance.mSwitchingUnawareRotationList.mImeSubtypeList,
+                                switchingUnawareImeSubtypes)) {
+                    // Can reuse the current instance.
+                    switchingUnawareRotationList = currentInstance.mSwitchingUnawareRotationList;
+                }
+                if (switchingUnawareRotationList == null) {
+                    switchingUnawareRotationList =
+                            new StaticRotationList(switchingUnawareImeSubtypes);
+                }
+            }
+
+            return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList);
+        }
+
+        private ControllerImpl(final DynamicRotationList switchingAwareRotationList,
+                final StaticRotationList switchingUnawareRotationList) {
+            mSwitchingAwareRotationList = switchingAwareRotationList;
+            mSwitchingUnawareRotationList = switchingUnawareRotationList;
+        }
+
+        public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi,
+                InputMethodSubtype subtype) {
+            if (imi == null) {
+                return null;
+            }
+            if (imi.supportsSwitchingToNextInputMethod()) {
+                return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
+                        subtype);
+            } else {
+                return mSwitchingUnawareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
+                        subtype);
+            }
+        }
+
+        public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) {
+            if (imi == null) {
+                return;
+            }
+            if (imi.supportsSwitchingToNextInputMethod()) {
+                mSwitchingAwareRotationList.onUserAction(imi, subtype);
+            }
+        }
+
+        private static List<ImeSubtypeListItem> filterImeSubtypeList(
+                final List<ImeSubtypeListItem> items,
+                final boolean supportsSwitchingToNextInputMethod) {
+            final ArrayList<ImeSubtypeListItem> result = new ArrayList<>();
+            final int ALL_ITEMS_COUNT = items.size();
+            for (int i = 0; i < ALL_ITEMS_COUNT; i++) {
+                final ImeSubtypeListItem item = items.get(i);
+                if (item.mImi.supportsSwitchingToNextInputMethod() ==
+                        supportsSwitchingToNextInputMethod) {
+                    result.add(item);
+                }
+            }
+            return result;
+        }
+    }
+
+    private final InputMethodSettings mSettings;
+    private InputMethodAndSubtypeList mSubtypeList;
+    private ControllerImpl mController;
+
     private InputMethodSubtypeSwitchingController(InputMethodSettings settings, Context context) {
         mSettings = settings;
         resetCircularListLocked(context);
@@ -269,19 +468,31 @@
         return new InputMethodSubtypeSwitchingController(settings, context);
     }
 
-    // TODO: write unit tests for this method and the logic that determines the next subtype
-    public void onCommitTextLocked(InputMethodInfo imi, InputMethodSubtype subtype) {
-        // TODO: Implement this.
+    public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) {
+        if (mController == null) {
+            if (DEBUG) {
+                Log.e(TAG, "mController shouldn't be null.");
+            }
+            return;
+        }
+        mController.onUserActionLocked(imi, subtype);
     }
 
     public void resetCircularListLocked(Context context) {
         mSubtypeList = new InputMethodAndSubtypeList(context, mSettings);
+        mController = ControllerImpl.createFrom(mController,
+                mSubtypeList.getSortedInputMethodAndSubtypeList());
     }
 
-    public ImeSubtypeListItem getNextInputMethodLocked(
-            boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) {
-        return getNextInputMethodLockedImpl(mSubtypeList.getSortedInputMethodAndSubtypeList(),
-                onlyCurrentIme, imi, subtype);
+    public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi,
+            InputMethodSubtype subtype) {
+        if (mController == null) {
+            if (DEBUG) {
+                Log.e(TAG, "mController shouldn't be null.");
+            }
+            return null;
+        }
+        return mController.getNextInputMethod(onlyCurrentIme, imi, subtype);
     }
 
     public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(boolean showSubtypes,
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index 0d00f41..73d3738 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -17,8 +17,10 @@
 package com.android.internal.net;
 
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -45,7 +47,10 @@
 
     public static Intent getIntentForConfirmation() {
         Intent intent = new Intent();
-        intent.setClassName(DIALOGS_PACKAGE, DIALOGS_PACKAGE + ".ConfirmDialog");
+        ComponentName componentName = ComponentName.unflattenFromString(
+                Resources.getSystem().getString(
+                        com.android.internal.R.string.config_customVpnConfirmDialogComponent));
+        intent.setClassName(componentName.getPackageName(), componentName.getClassName());
         return intent;
     }
 
diff --git a/core/java/com/android/internal/os/SomeArgs.java b/core/java/com/android/internal/os/SomeArgs.java
index 7edf4cc..c977997 100644
--- a/core/java/com/android/internal/os/SomeArgs.java
+++ b/core/java/com/android/internal/os/SomeArgs.java
@@ -45,6 +45,7 @@
     public Object arg3;
     public Object arg4;
     public Object arg5;
+    public Object arg6;
     public int argi1;
     public int argi2;
     public int argi3;
@@ -95,6 +96,7 @@
         arg3 = null;
         arg4 = null;
         arg5 = null;
+        arg6 = null;
         argi1 = 0;
         argi2 = 0;
         argi3 = 0;
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index a56fa36..d66ef83 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -169,6 +169,15 @@
         return false;
     }
 
+    public static boolean contains(long[] array, long value) {
+        for (long element : array) {
+            if (element == value) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public static long total(long[] array) {
         long total = 0;
         for (long value : array) {
@@ -229,6 +238,14 @@
         return array;
     }
 
+    /**
+     * Appends a new value to a copy of the array and returns the copy.  If
+     * the value is already present, the original array is returned
+     * @param cur The original array, or null to represent an empty array.
+     * @param val The value to add.
+     * @return A new array that contains all of the values of the original array
+     * with the new value added, or the original array.
+     */
     public static int[] appendInt(int[] cur, int val) {
         if (cur == null) {
             return new int[] { val };
@@ -264,4 +281,48 @@
         }
         return cur;
     }
+
+    /**
+     * Appends a new value to a copy of the array and returns the copy.  If
+     * the value is already present, the original array is returned
+     * @param cur The original array, or null to represent an empty array.
+     * @param val The value to add.
+     * @return A new array that contains all of the values of the original array
+     * with the new value added, or the original array.
+     */
+    public static long[] appendLong(long[] cur, long val) {
+        if (cur == null) {
+            return new long[] { val };
+        }
+        final int N = cur.length;
+        for (int i = 0; i < N; i++) {
+            if (cur[i] == val) {
+                return cur;
+            }
+        }
+        long[] ret = new long[N + 1];
+        System.arraycopy(cur, 0, ret, 0, N);
+        ret[N] = val;
+        return ret;
+    }
+
+    public static long[] removeLong(long[] cur, long val) {
+        if (cur == null) {
+            return null;
+        }
+        final int N = cur.length;
+        for (int i = 0; i < N; i++) {
+            if (cur[i] == val) {
+                long[] ret = new long[N - 1];
+                if (i > 0) {
+                    System.arraycopy(cur, 0, ret, 0, i);
+                }
+                if (i < (N - 1)) {
+                    System.arraycopy(cur, i + 1, ret, i, N - i - 1);
+                }
+                return ret;
+            }
+        }
+        return cur;
+    }
 }
diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
index 9e8d12b..b100d27 100644
--- a/core/java/com/android/internal/view/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -28,4 +28,5 @@
     void onUnbindMethod(int sequence);
     void setActive(boolean active);
     void setCursorAnchorMonitorMode(int monitorMode);
+    void setUserActionNotificationSequenceNumber(int sequenceNumber);
 }
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 5336174..b84c359 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -77,6 +77,6 @@
     boolean setInputMethodEnabled(String id, boolean enabled);
     void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
     int getInputMethodWindowVisibleHeight();
-    oneway void notifyTextCommitted();
+    oneway void notifyUserAction(int sequenceNumber);
     void setCursorAnchorMonitorMode(in IBinder token, int monitorMode);
 }
diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java
index 14afe21..3a3e56d 100644
--- a/core/java/com/android/internal/view/InputBindResult.java
+++ b/core/java/com/android/internal/view/InputBindResult.java
@@ -47,13 +47,19 @@
      * Sequence number of this binding.
      */
     public final int sequence;
-    
+
+    /**
+     * Sequence number of user action notification.
+     */
+    public final int userActionNotificationSequenceNumber;
+
     public InputBindResult(IInputMethodSession _method, InputChannel _channel,
-            String _id, int _sequence) {
+            String _id, int _sequence, int _userActionNotificationSequenceNumber) {
         method = _method;
         channel = _channel;
         id = _id;
         sequence = _sequence;
+        userActionNotificationSequenceNumber = _userActionNotificationSequenceNumber;
     }
     
     InputBindResult(Parcel source) {
@@ -65,12 +71,15 @@
         }
         id = source.readString();
         sequence = source.readInt();
+        userActionNotificationSequenceNumber = source.readInt();
     }
 
     @Override
     public String toString() {
         return "InputBindResult{" + method + " " + id
-                + " #" + sequence + "}";
+                + " sequence:" + sequence
+                + " userActionNotificationSequenceNumber:" + userActionNotificationSequenceNumber
+                + "}";
     }
 
     /**
@@ -90,6 +99,7 @@
         }
         dest.writeString(id);
         dest.writeInt(sequence);
+        dest.writeInt(userActionNotificationSequenceNumber);
     }
 
     /**
diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
index bed0b88..7fb2efd 100644
--- a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
+++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
@@ -480,7 +480,7 @@
 
     private void ensureSpinner() {
         if (mSpinner == null) {
-            mSpinner = new Spinner(getContext());
+            mSpinner = new Spinner(getContext(), null, R.attr.actionDropDownStyle);
             Toolbar.LayoutParams lp = new Toolbar.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                     ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL);
             mSpinner.setLayoutParams(lp);
diff --git a/core/java/com/android/server/SystemService.java b/core/java/com/android/server/SystemService.java
index bf36bb1..43a05d0 100644
--- a/core/java/com/android/server/SystemService.java
+++ b/core/java/com/android/server/SystemService.java
@@ -193,58 +193,4 @@
     private SystemServiceManager getManager() {
         return LocalServices.getService(SystemServiceManager.class);
     }
-
-//    /**
-//     * Called when a new user has been created. If your service deals with multiple users, this
-//     * method should be overridden.
-//     *
-//     * @param userHandle The user that was created.
-//     */
-//    public void onUserCreated(int userHandle) {
-//    }
-//
-//    /**
-//     * Called when an existing user has started a new session. If your service deals with multiple
-//     * users, this method should be overridden.
-//     *
-//     * @param userHandle The user who started a new session.
-//     */
-//    public void onUserStarted(int userHandle) {
-//    }
-//
-//    /**
-//     * Called when a background user session has entered the foreground. If your service deals with
-//     * multiple users, this method should be overridden.
-//     *
-//     * @param userHandle The user who's session entered the foreground.
-//     */
-//    public void onUserForeground(int userHandle) {
-//    }
-//
-//    /**
-//     * Called when a foreground user session has entered the background. If your service deals with
-//     * multiple users, this method should be overridden;
-//     *
-//     * @param userHandle The user who's session entered the background.
-//     */
-//    public void onUserBackground(int userHandle) {
-//    }
-//
-//    /**
-//     * Called when a user's active session has stopped. If your service deals with multiple users,
-//     * this method should be overridden.
-//     *
-//     * @param userHandle The user who's session has stopped.
-//     */
-//    public void onUserStopped(int userHandle) {
-//    }
-//
-//    /**
-//     * Called when a user has been removed from the system. If your service deals with multiple
-//     * users, this method should be overridden.
-//     *
-//     * @param userHandle The user who has been removed.
-//     */
-//    public void onUserRemoved(int userHandle) {
-//    }
 }
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index f446c3a..15dfed1 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -57,7 +57,6 @@
 	android_view_KeyEvent.cpp \
 	android_view_KeyCharacterMap.cpp \
 	android_view_GraphicBuffer.cpp \
-	android_view_GLRenderer.cpp \
 	android_view_GLES20Canvas.cpp \
 	android_view_HardwareLayer.cpp \
 	android_view_ThreadedRenderer.cpp \
@@ -90,6 +89,7 @@
 	android_util_Process.cpp \
 	android_util_StringBlock.cpp \
 	android_util_XmlBlock.cpp \
+	android/graphics/AndroidPicture.cpp \
 	android/graphics/AutoDecodeCancel.cpp \
 	android/graphics/Bitmap.cpp \
 	android/graphics/BitmapFactory.cpp \
@@ -141,6 +141,7 @@
 	android_hardware_camera2_DngCreator.cpp \
 	android_hardware_SensorManager.cpp \
 	android_hardware_SerialPort.cpp \
+	android_hardware_SoundTrigger.cpp \
 	android_hardware_UsbDevice.cpp \
 	android_hardware_UsbDeviceConnection.cpp \
 	android_hardware_UsbRequest.cpp \
@@ -236,6 +237,7 @@
 	libpdfium \
 	libimg_utils \
 	libnetd_client \
+	libsoundtrigger
 
 ifeq ($(USE_OPENGL_RENDERER),true)
 	LOCAL_SHARED_LIBRARIES += libhwui
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index e069876..f2b9bac 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -29,7 +29,6 @@
 
 #include <SkGraphics.h>
 #include <SkImageDecoder.h>
-#include <SkImageRef_GlobalPool.h>
 
 #include "jni.h"
 #include "JNIHelp.h"
@@ -83,6 +82,7 @@
 extern int register_android_hardware_camera2_DngCreator(JNIEnv *env);
 extern int register_android_hardware_SensorManager(JNIEnv *env);
 extern int register_android_hardware_SerialPort(JNIEnv *env);
+extern int register_android_hardware_SoundTrigger(JNIEnv *env);
 extern int register_android_hardware_UsbDevice(JNIEnv *env);
 extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env);
 extern int register_android_hardware_UsbRequest(JNIEnv *env);
@@ -130,7 +130,6 @@
 extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
 extern int register_android_view_GraphicBuffer(JNIEnv* env);
 extern int register_android_view_GLES20Canvas(JNIEnv* env);
-extern int register_android_view_GLRenderer(JNIEnv* env);
 extern int register_android_view_HardwareLayer(JNIEnv* env);
 extern int register_android_view_ThreadedRenderer(JNIEnv* env);
 extern int register_android_view_Surface(JNIEnv* env);
@@ -245,14 +244,6 @@
         mArgBlockLength(argBlockLength)
 {
     SkGraphics::Init();
-    // this sets our preference for 16bit images during decode
-    // in case the src is opaque and 24bit
-    SkImageDecoder::SetDeviceConfig(SkBitmap::kRGB_565_Config);
-    // This cache is shared between browser native images, and java "purgeable"
-    // bitmaps. This globalpool is for images that do not either use the java
-    // heap, or are not backed by ashmem. See BitmapFactory.cpp for the key
-    // java call site.
-    SkImageRef_GlobalPool::SetRAMBudget(512 * 1024);
     // There is also a global font cache, but its budget is specified in code
     // see SkFontHost_android.cpp
 
@@ -499,6 +490,8 @@
     char profile_duration[sizeof("-Xprofile-duration:") + PROPERTY_VALUE_MAX];
     char profile_interval[sizeof("-Xprofile-interval:") + PROPERTY_VALUE_MAX];
     char profile_backoff[sizeof("-Xprofile-backoff:") + PROPERTY_VALUE_MAX];
+    char profile_top_k_threshold[sizeof("-Xprofile-top-k-threshold") + PROPERTY_VALUE_MAX];
+    char profile_top_k_change_threshold[sizeof("-Xprofile-top-k-change-threshold") + PROPERTY_VALUE_MAX];
     char langOption[sizeof("-Duser.language=") + 3];
     char regionOption[sizeof("-Duser.region=") + 3];
     char lockProfThresholdBuf[sizeof("-Xlockprofthreshold:") + sizeof(propBuf)];
@@ -822,31 +815,65 @@
      * Set profiler options
      */
     if (libart) {
-      // Number of seconds during profile runs.
-      strcpy(profile_period, "-Xprofile-period:");
-      property_get("dalvik.vm.profile.period_secs", profile_period+17, "10");
-      opt.optionString = profile_period;
-      mOptions.add(opt);
+        // Whether or not the profiler should be enabled.
+        property_get("dalvik.vm.profiler", propBuf, "0");
+        if (propBuf[0] == '1') {
+            opt.optionString = "-Xenable-profiler";
+            mOptions.add(opt);
+        }
 
-      // Length of each profile run (seconds).
-      strcpy(profile_duration, "-Xprofile-duration:");
-      property_get("dalvik.vm.profile.duration_secs", profile_duration+19, "30");
-      opt.optionString = profile_duration;
-      mOptions.add(opt);
+        // Whether the profile should start upon app startup or be delayed by some random offset
+        // (in seconds) that is bound between 0 and a fixed value.
+        property_get("dalvik.vm.profile.start-immed", propBuf, "0");
+        if (propBuf[0] == '1') {
+            opt.optionString = "-Xprofile-start-immediately";
+            mOptions.add(opt);
+        }
 
+        // Number of seconds during profile runs.
+        strcpy(profile_period, "-Xprofile-period:");
+        if (property_get("dalvik.vm.profile.period-secs", profile_period+17, NULL) > 0) {
+           opt.optionString = profile_period;
+           mOptions.add(opt);
+        }
 
-      // Polling interval during profile run (microseconds).
-      strcpy(profile_interval, "-Xprofile-interval:");
-      property_get("dalvik.vm.profile.interval_us", profile_interval+19, "10000");
-      opt.optionString = profile_interval;
-      mOptions.add(opt);
+        // Length of each profile run (seconds).
+        strcpy(profile_duration, "-Xprofile-duration:");
+        if (property_get("dalvik.vm.profile.duration-secs", profile_duration+19, NULL) > 0) {
+            opt.optionString = profile_duration;
+            mOptions.add(opt);
+        }
 
-      // Coefficient for period backoff.  The the period is multiplied
-      // by this value after each profile run.
-      strcpy(profile_backoff, "-Xprofile-backoff:");
-      property_get("dalvik.vm.profile.backoff_coeff", profile_backoff+18, "2.0");
-      opt.optionString = profile_backoff;
-      mOptions.add(opt);
+        // Polling interval during profile run (microseconds).
+        strcpy(profile_interval, "-Xprofile-interval:");
+        if (property_get("dalvik.vm.profile.interval-us", profile_interval+19, NULL) > 0) {
+            opt.optionString = profile_interval;
+            mOptions.add(opt);
+        }
+
+        // Coefficient for period backoff.  The the period is multiplied
+        // by this value after each profile run.
+        strcpy(profile_backoff, "-Xprofile-backoff:");
+        if (property_get("dalvik.vm.profile.backoff-coeff", profile_backoff+18, NULL) > 0) {
+            opt.optionString = profile_backoff;
+            mOptions.add(opt);
+        }
+
+        // Top K% of samples that are considered relevant when deciding if the app should be recompiled.
+        strcpy(profile_top_k_threshold, "-Xprofile-top-k-threshold:");
+        if (property_get("dalvik.vm.profile.top-k-thr", profile_top_k_threshold+26, NULL) > 0) {
+            opt.optionString = profile_top_k_threshold;
+            mOptions.add(opt);
+        }
+
+        // The threshold after which a change in the structure of the top K% profiled samples becomes significant
+        // and triggers recompilation. A change in profile is considered significant if X% (top-k-change-threshold)
+        // of the top K% (top-k-threshold property) samples has changed.
+        strcpy(profile_top_k_change_threshold, "-Xprofile-top-k-change-threshold:");
+        if (property_get("dalvik.vm.profile.top-k-ch-thr", profile_top_k_change_threshold+33, NULL) > 0) {
+            opt.optionString = profile_top_k_change_threshold;
+            mOptions.add(opt);
+        }
     }
 
     initArgs.version = JNI_VERSION_1_4;
@@ -1214,7 +1241,6 @@
     REG_JNI(register_android_view_RenderNodeAnimator),
     REG_JNI(register_android_view_GraphicBuffer),
     REG_JNI(register_android_view_GLES20Canvas),
-    REG_JNI(register_android_view_GLRenderer),
     REG_JNI(register_android_view_HardwareLayer),
     REG_JNI(register_android_view_ThreadedRenderer),
     REG_JNI(register_android_view_Surface),
@@ -1290,6 +1316,7 @@
     REG_JNI(register_android_hardware_camera2_DngCreator),
     REG_JNI(register_android_hardware_SensorManager),
     REG_JNI(register_android_hardware_SerialPort),
+    REG_JNI(register_android_hardware_SoundTrigger),
     REG_JNI(register_android_hardware_UsbDevice),
     REG_JNI(register_android_hardware_UsbDeviceConnection),
     REG_JNI(register_android_hardware_UsbRequest),
diff --git a/core/jni/android/graphics/AndroidPicture.cpp b/core/jni/android/graphics/AndroidPicture.cpp
new file mode 100644
index 0000000..5977ab2
--- /dev/null
+++ b/core/jni/android/graphics/AndroidPicture.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AndroidPicture.h"
+#include "SkCanvas.h"
+#include "SkStream.h"
+
+AndroidPicture::AndroidPicture(const AndroidPicture* src) {
+    if (NULL != src) {
+        mWidth = src->width();
+        mHeight = src->height();
+        if (NULL != src->mPicture.get()) {
+            mPicture.reset(SkRef(src->mPicture.get()));
+        } if (NULL != src->mRecorder.get()) {
+            mPicture.reset(src->makePartialCopy());
+        }
+    } else {
+        mWidth = 0;
+        mHeight = 0;
+    }
+}
+
+SkCanvas* AndroidPicture::beginRecording(int width, int height) {
+    mPicture.reset(NULL);
+    mRecorder.reset(new SkPictureRecorder);
+    mWidth = width;
+    mHeight = height;
+    return mRecorder->beginRecording(width, height, NULL, 0);
+}
+
+void AndroidPicture::endRecording() {
+    if (NULL != mRecorder.get()) {
+        mPicture.reset(mRecorder->endRecording());
+        mRecorder.reset(NULL);
+    }
+}
+
+int AndroidPicture::width() const {
+    if (NULL != mPicture.get()) {
+        SkASSERT(mPicture->width() == mWidth);
+        SkASSERT(mPicture->height() == mHeight);
+    }
+
+    return mWidth;
+}
+
+int AndroidPicture::height() const {
+    if (NULL != mPicture.get()) {
+        SkASSERT(mPicture->width() == mWidth);
+        SkASSERT(mPicture->height() == mHeight);
+    }
+
+    return mHeight;
+}
+
+AndroidPicture* AndroidPicture::CreateFromStream(SkStream* stream) {
+    AndroidPicture* newPict = new AndroidPicture;
+
+    newPict->mPicture.reset(SkPicture::CreateFromStream(stream));
+    if (NULL != newPict->mPicture.get()) {
+        newPict->mWidth = newPict->mPicture->width();
+        newPict->mHeight = newPict->mPicture->height();
+    }
+
+    return newPict;
+}
+
+void AndroidPicture::serialize(SkWStream* stream) const {
+    if (NULL != mRecorder.get()) {
+        SkAutoTDelete<SkPicture> tempPict(this->makePartialCopy());
+        tempPict->serialize(stream);
+    } else if (NULL != mPicture.get()) {
+        mPicture->serialize(stream);
+    } else {
+        SkPicture empty;
+        empty.serialize(stream);
+    }
+}
+
+void AndroidPicture::draw(SkCanvas* canvas) {
+    if (NULL != mRecorder.get()) {
+        this->endRecording();
+        SkASSERT(NULL != mPicture.get());
+    }
+    if (NULL != mPicture.get()) {
+        // TODO: remove this const_cast once pictures are immutable
+        const_cast<SkPicture*>(mPicture.get())->draw(canvas);
+    }
+}
+
+SkPicture* AndroidPicture::makePartialCopy() const {
+    SkASSERT(NULL != mRecorder.get());
+
+    SkPictureRecorder reRecorder;
+
+    SkCanvas* canvas = reRecorder.beginRecording(mWidth, mHeight, NULL, 0);
+    mRecorder->partialReplay(canvas);
+    return reRecorder.endRecording();
+}
diff --git a/core/jni/android/graphics/AndroidPicture.h b/core/jni/android/graphics/AndroidPicture.h
new file mode 100644
index 0000000..f434941
--- /dev/null
+++ b/core/jni/android/graphics/AndroidPicture.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_PICTURE_H
+#define ANDROID_PICTURE_H
+
+#include "SkPicture.h"
+#include "SkPictureRecorder.h"
+#include "SkRefCnt.h"
+#include "SkTemplates.h"
+
+class SkCanvas;
+class SkPicture;
+class SkPictureRecorder;
+class SkStream;
+class SkWStream;
+
+// Skia's SkPicture class has been split into an SkPictureRecorder
+// and an SkPicture. AndroidPicture recreates the functionality
+// of the old SkPicture interface by flip-flopping between the two
+// new classes.
+class AndroidPicture {
+public:
+    explicit AndroidPicture(const AndroidPicture* src = NULL);
+
+    SkCanvas* beginRecording(int width, int height);
+
+    void endRecording();
+
+    int width() const;
+
+    int height() const;
+
+    static AndroidPicture* CreateFromStream(SkStream* stream);
+
+    void serialize(SkWStream* stream) const;
+
+    void draw(SkCanvas* canvas);
+
+private:
+    int mWidth;
+    int mHeight;
+    SkAutoTUnref<const SkPicture> mPicture;
+    SkAutoTDelete<SkPictureRecorder> mRecorder;
+
+    // Make a copy of a picture that is in the midst of being recorded. The
+    // resulting picture will have balanced saves and restores.
+    SkPicture* makePartialCopy() const;
+};
+#endif // ANDROID_PICTURE_H
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 0328517..9998995 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -361,24 +361,50 @@
 }
 
 static void Bitmap_reconfigure(JNIEnv* env, jobject clazz, jlong bitmapHandle,
-        jint width, jint height, jint configHandle, jint allocSize) {
+        jint width, jint height, jint configHandle, jint allocSize,
+        jboolean requestPremul) {
     SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
     SkBitmap::Config config = static_cast<SkBitmap::Config>(configHandle);
-    if (width * height * SkBitmap::ComputeBytesPerPixel(config) > allocSize) {
+    SkColorType colorType = SkBitmapConfigToColorType(config);
+
+    // ARGB_4444 is a deprecated format, convert automatically to 8888
+    if (colorType == kARGB_4444_SkColorType) {
+        colorType = kN32_SkColorType;
+    }
+
+    if (width * height * SkColorTypeBytesPerPixel(colorType) > allocSize) {
         // done in native as there's no way to get BytesPerPixel in Java
         doThrowIAE(env, "Bitmap not large enough to support new configuration");
         return;
     }
     SkPixelRef* ref = bitmap->pixelRef();
-    SkSafeRef(ref);
-    bitmap->setConfig(config, width, height);
+    ref->ref();
+    SkAlphaType alphaType;
+    if (bitmap->colorType() != kRGB_565_SkColorType
+            && bitmap->alphaType() == kOpaque_SkAlphaType) {
+        // If the original bitmap was set to opaque, keep that setting, unless it
+        // was 565, which is required to be opaque.
+        alphaType = kOpaque_SkAlphaType;
+    } else {
+        // Otherwise respect the premultiplied request.
+        alphaType = requestPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
+    }
+    bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType));
+    // FIXME: Skia thinks of an SkPixelRef as having a constant SkImageInfo (except for
+    // its alphatype), so it would make more sense from Skia's perspective to create a
+    // new SkPixelRef. That said, libhwui uses the pointer to the SkPixelRef as a key
+    // for its cache, so it won't realize this is the same Java Bitmap.
+    SkImageInfo& info = const_cast<SkImageInfo&>(ref->info());
+    // Use the updated from the SkBitmap, which may have corrected an invalid alphatype.
+    // (e.g. 565 non-opaque)
+    info = bitmap->info();
     bitmap->setPixelRef(ref);
 
     // notifyPixelsChanged will increment the generation ID even though the actual pixel data
     // hasn't been touched. This signals the renderer that the bitmap (including width, height,
-    // and config) has changed.
+    // colortype and alphatype) has changed.
     ref->notifyPixelsChanged();
-    SkSafeUnref(ref);
+    ref->unref();
 }
 
 // These must match the int values in Bitmap.java
@@ -799,7 +825,7 @@
         (void*)Bitmap_copy },
     {   "nativeDestructor",         "(J)V", (void*)Bitmap_destructor },
     {   "nativeRecycle",            "(J)Z", (void*)Bitmap_recycle },
-    {   "nativeReconfigure",        "(JIIII)V", (void*)Bitmap_reconfigure },
+    {   "nativeReconfigure",        "(JIIIIZ)V", (void*)Bitmap_reconfigure },
     {   "nativeCompress",           "(JIILjava/io/OutputStream;[B)Z",
         (void*)Bitmap_compress },
     {   "nativeErase",              "(JI)V", (void*)Bitmap_erase },
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 7aa241a..5106f0d 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -2,11 +2,8 @@
 
 #include "BitmapFactory.h"
 #include "NinePatchPeeker.h"
-#include "SkData.h"
 #include "SkFrontBufferedStream.h"
 #include "SkImageDecoder.h"
-#include "SkImageRef_ashmem.h"
-#include "SkImageRef_GlobalPool.h"
 #include "SkMath.h"
 #include "SkPixelRef.h"
 #include "SkStream.h"
@@ -32,8 +29,6 @@
 jfieldID gOptions_premultipliedFieldID;
 jfieldID gOptions_mutableFieldID;
 jfieldID gOptions_ditherFieldID;
-jfieldID gOptions_purgeableFieldID;
-jfieldID gOptions_shareableFieldID;
 jfieldID gOptions_preferQualityOverSpeedFieldID;
 jfieldID gOptions_scaledFieldID;
 jfieldID gOptions_densityFieldID;
@@ -90,14 +85,6 @@
     return jstr;
 }
 
-static bool optionsPurgeable(JNIEnv* env, jobject options) {
-    return options != NULL && env->GetBooleanField(options, gOptions_purgeableFieldID);
-}
-
-static bool optionsShareable(JNIEnv* env, jobject options) {
-    return options != NULL && env->GetBooleanField(options, gOptions_shareableFieldID);
-}
-
 static bool optionsJustBounds(JNIEnv* env, jobject options) {
     return options != NULL && env->GetBooleanField(options, gOptions_justBoundsFieldID);
 }
@@ -125,28 +112,6 @@
     }
 }
 
-static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStreamRewindable* stream,
-        int sampleSize, bool ditherImage) {
-
-    SkImageInfo bitmapInfo;
-    if (!bitmap->asImageInfo(&bitmapInfo)) {
-        ALOGW("bitmap has unknown configuration so no memory has been allocated");
-        return NULL;
-    }
-
-    SkImageRef* pr;
-    // only use ashmem for large images, since mmaps come at a price
-    if (bitmap->getSize() >= 32 * 1024) {
-        pr = new SkImageRef_ashmem(bitmapInfo, stream, sampleSize);
-    } else {
-        pr = new SkImageRef_GlobalPool(bitmapInfo, stream, sampleSize);
-    }
-    pr->setDitherImage(ditherImage);
-    bitmap->setPixelRef(pr)->unref();
-    pr->isOpaque(bitmap);
-    return pr;
-}
-
 static SkColorType colorTypeForScaledOutput(SkColorType colorType) {
     switch (colorType) {
         case kUnknown_SkColorType:
@@ -230,21 +195,17 @@
     const unsigned int mSize;
 };
 
-// since we "may" create a purgeable imageref, we require the stream be ref'able
-// i.e. dynamically allocated, since its lifetime may exceed the current stack
-// frame.
 static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding,
-        jobject options, bool allowPurgeable, bool forcePurgeable = false) {
+        jobject options) {
 
     int sampleSize = 1;
 
-    SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode;
+    SkImageDecoder::Mode decodeMode = SkImageDecoder::kDecodePixels_Mode;
     SkBitmap::Config prefConfig = SkBitmap::kARGB_8888_Config;
 
     bool doDither = true;
     bool isMutable = false;
     float scale = 1.0f;
-    bool isPurgeable = forcePurgeable || (allowPurgeable && optionsPurgeable(env, options));
     bool preferQualityOverSpeed = false;
     bool requireUnpremultiplied = false;
 
@@ -253,7 +214,7 @@
     if (options != NULL) {
         sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
         if (optionsJustBounds(env, options)) {
-            mode = SkImageDecoder::kDecodeBounds_Mode;
+            decodeMode = SkImageDecoder::kDecodeBounds_Mode;
         }
 
         // initialize these, in case we fail later on
@@ -281,7 +242,6 @@
     }
 
     const bool willScale = scale != 1.0f;
-    isPurgeable &= !willScale;
 
     SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
     if (decoder == NULL) {
@@ -312,8 +272,6 @@
     NinePatchPeeker peeker(decoder);
     decoder->setPeeker(&peeker);
 
-    SkImageDecoder::Mode decodeMode = isPurgeable ? SkImageDecoder::kDecodeBounds_Mode : mode;
-
     JavaPixelAllocator javaAllocator(env);
     RecyclingPixelAllocator recyclingAllocator(outputBitmap->pixelRef(), existingBufferSize);
     ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
@@ -354,7 +312,7 @@
     int scaledWidth = decodingBitmap.width();
     int scaledHeight = decodingBitmap.height();
 
-    if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) {
+    if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
         scaledWidth = int(scaledWidth * scale + 0.5f);
         scaledHeight = int(scaledHeight * scale + 0.5f);
     }
@@ -368,7 +326,7 @@
     }
 
     // if we're in justBounds mode, return now (skip the java bitmap)
-    if (mode == SkImageDecoder::kDecodeBounds_Mode) {
+    if (decodeMode == SkImageDecoder::kDecodeBounds_Mode) {
         return NULL;
     }
 
@@ -460,21 +418,15 @@
         }
     }
 
-    SkPixelRef* pr;
-    if (isPurgeable) {
-        pr = installPixelRef(outputBitmap, stream, sampleSize, doDither);
-    } else {
-        // if we get here, we're in kDecodePixels_Mode and will therefore
-        // already have a pixelref installed.
-        pr = outputBitmap->pixelRef();
-    }
-    if (pr == NULL) {
+    // if we get here, we're in kDecodePixels_Mode and will therefore
+    // already have a pixelref installed.
+    if (outputBitmap->pixelRef() == NULL) {
         return nullObjectReturn("Got null SkPixelRef");
     }
 
     if (!isMutable && javaBitmap == NULL) {
         // promise we will never change our pixels (great for sharing and pictures)
-        pr->setImmutable();
+        outputBitmap->setImmutable();
     }
 
     // detach bitmap from its autodeleter, since we want to own it now
@@ -513,8 +465,7 @@
         SkAutoTUnref<SkStreamRewindable> bufferedStream(
                 SkFrontBufferedStream::Create(stream, BYTES_TO_BUFFER));
         SkASSERT(bufferedStream.get() != NULL);
-        // for now we don't allow purgeable with java inputstreams
-        bitmap = doDecode(env, bufferedStream, padding, options, false, false);
+        bitmap = doDecode(env, bufferedStream, padding, options);
     }
     return bitmap;
 }
@@ -543,76 +494,33 @@
     SkAutoTUnref<SkFILEStream> fileStream(new SkFILEStream(file,
                          SkFILEStream::kCallerRetains_Ownership));
 
-    SkAutoTUnref<SkStreamRewindable> stream;
+    // Use a buffered stream. Although an SkFILEStream can be rewound, this
+    // ensures that SkImageDecoder::Factory never rewinds beyond the
+    // current position of the file descriptor.
+    SkAutoTUnref<SkStreamRewindable> stream(SkFrontBufferedStream::Create(fileStream,
+            BYTES_TO_BUFFER));
 
-    // Retain the old behavior of allowing purgeable if both purgeable and
-    // shareable are set to true.
-    bool isPurgeable = optionsPurgeable(env, bitmapFactoryOptions)
-                       && optionsShareable(env, bitmapFactoryOptions);
-    if (isPurgeable) {
-        // Copy the stream, so the image can be decoded multiple times without
-        // continuing to modify the original file descriptor.
-        // Copy beginning from the current position.
-        const size_t fileSize = fileStream->getLength() - fileStream->getPosition();
-        void* buffer = sk_malloc_flags(fileSize, 0);
-        if (buffer == NULL) {
-            return nullObjectReturn("Could not make a copy for ashmem");
-        }
-
-        SkAutoTUnref<SkData> data(SkData::NewFromMalloc(buffer, fileSize));
-
-        if (fileStream->read(buffer, fileSize) != fileSize) {
-            return nullObjectReturn("Could not read the file.");
-        }
-
-        stream.reset(new SkMemoryStream(data));
-    } else {
-        // Use a buffered stream. Although an SkFILEStream can be rewound, this
-        // ensures that SkImageDecoder::Factory never rewinds beyond the
-        // current position of the file descriptor.
-        stream.reset(SkFrontBufferedStream::Create(fileStream, BYTES_TO_BUFFER));
-    }
-
-    return doDecode(env, stream, padding, bitmapFactoryOptions, isPurgeable);
+    return doDecode(env, stream, padding, bitmapFactoryOptions);
 }
 
 static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jlong native_asset,
         jobject padding, jobject options) {
 
-    SkStreamRewindable* stream;
     Asset* asset = reinterpret_cast<Asset*>(native_asset);
-    bool forcePurgeable = optionsPurgeable(env, options);
-    if (forcePurgeable) {
-        // if we could "ref/reopen" the asset, we may not need to copy it here
-        // and we could assume optionsShareable, since assets are always RO
-        stream = CopyAssetToStream(asset);
-        if (stream == NULL) {
-            return NULL;
-        }
-    } else {
-        // since we know we'll be done with the asset when we return, we can
-        // just use a simple wrapper
-        stream = new AssetStreamAdaptor(asset,
-                                        AssetStreamAdaptor::kNo_OwnAsset,
-                                        AssetStreamAdaptor::kNo_HasMemoryBase);
-    }
-    SkAutoUnref aur(stream);
-    return doDecode(env, stream, padding, options, forcePurgeable, forcePurgeable);
+    // since we know we'll be done with the asset when we return, we can
+    // just use a simple wrapper
+    SkAutoTUnref<SkStreamRewindable> stream(new AssetStreamAdaptor(asset,
+            AssetStreamAdaptor::kNo_OwnAsset, AssetStreamAdaptor::kNo_HasMemoryBase));
+    return doDecode(env, stream, padding, options);
 }
 
 static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
         jint offset, jint length, jobject options) {
 
-    /*  If optionsShareable() we could decide to just wrap the java array and
-        share it, but that means adding a globalref to the java array object
-        and managing its lifetime. For now we just always copy the array's data
-        if optionsPurgeable(), unless we're just decoding bounds.
-     */
-    bool purgeable = optionsPurgeable(env, options) && !optionsJustBounds(env, options);
     AutoJavaByteArray ar(env, byteArray);
-    SkMemoryStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable);
+    SkMemoryStream* stream = new SkMemoryStream(ar.ptr() + offset, length, false);
     SkAutoUnref aur(stream);
-    return doDecode(env, stream, NULL, options, purgeable);
+    return doDecode(env, stream, NULL, options);
 }
 
 static void nativeRequestCancel(JNIEnv*, jobject joptions) {
@@ -676,8 +584,6 @@
     gOptions_premultipliedFieldID = getFieldIDCheck(env, options_class, "inPremultiplied", "Z");
     gOptions_mutableFieldID = getFieldIDCheck(env, options_class, "inMutable", "Z");
     gOptions_ditherFieldID = getFieldIDCheck(env, options_class, "inDither", "Z");
-    gOptions_purgeableFieldID = getFieldIDCheck(env, options_class, "inPurgeable", "Z");
-    gOptions_shareableFieldID = getFieldIDCheck(env, options_class, "inInputShareable", "Z");
     gOptions_preferQualityOverSpeedFieldID = getFieldIDCheck(env, options_class,
             "inPreferQualityOverSpeed", "Z");
     gOptions_scaledFieldID = getFieldIDCheck(env, options_class, "inScaled", "Z");
diff --git a/core/jni/android/graphics/Camera.cpp b/core/jni/android/graphics/Camera.cpp
index ef57e3d..d17f46c 100644
--- a/core/jni/android/graphics/Camera.cpp
+++ b/core/jni/android/graphics/Camera.cpp
@@ -3,6 +3,8 @@
 
 #include "SkCamera.h"
 
+#include "GraphicsJNI.h"
+
 static jfieldID gNativeInstanceFieldID;
 
 static void Camera_constructor(JNIEnv* env, jobject obj) {
@@ -93,7 +95,7 @@
 }
 
 static void Camera_applyToCanvas(JNIEnv* env, jobject obj, jlong canvasHandle) {
-    SkCanvas* native_canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+    SkCanvas* native_canvas = GraphicsJNI::getNativeCanvas(canvasHandle);
     jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID);
     Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle);
     v->applyToCanvas((SkCanvas*)native_canvas);
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 432a615..6de3b9e 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -19,12 +19,14 @@
 #include <android_runtime/AndroidRuntime.h>
 
 #include "SkCanvas.h"
+#include "SkClipStack.h"
 #include "SkDevice.h"
+#include "SkDeque.h"
 #include "SkDrawFilter.h"
 #include "SkGraphics.h"
-#include "SkImageRef_GlobalPool.h"
 #include "SkPorterDuff.h"
 #include "SkShader.h"
+#include "SkTArray.h"
 #include "SkTemplates.h"
 
 #ifdef USE_MINIKIN
@@ -42,21 +44,6 @@
 
 #include <utils/Log.h>
 
-static uint32_t get_thread_msec() {
-#if defined(HAVE_POSIX_CLOCKS)
-    struct timespec tm;
-
-    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tm);
-
-    return tm.tv_sec * 1000LL + tm.tv_nsec / 1000000;
-#else
-    struct timeval tv;
-
-    gettimeofday(&tv, NULL);
-    return tv.tv_sec * 1000LL + tv.tv_usec / 1000;
-#endif
-}
-
 namespace android {
 
 class ClipCopier : public SkCanvas::ClipVisitor {
@@ -77,6 +64,155 @@
     SkCanvas* m_dstCanvas;
 };
 
+// Holds an SkCanvas reference plus additional native data.
+class NativeCanvasWrapper {
+private:
+    struct SaveRec {
+        int                 saveCount;
+        SkCanvas::SaveFlags saveFlags;
+    };
+
+public:
+    NativeCanvasWrapper(SkCanvas* canvas)
+        : mCanvas(canvas)
+        , mSaveStack(NULL) {
+        SkASSERT(canvas);
+    }
+
+    ~NativeCanvasWrapper() {
+        delete mSaveStack;
+    }
+
+    SkCanvas* getCanvas() const {
+        return mCanvas.get();
+    }
+
+    void setCanvas(SkCanvas* canvas) {
+        SkASSERT(canvas);
+        mCanvas.reset(canvas);
+
+        delete mSaveStack;
+        mSaveStack = NULL;
+    }
+
+    int save(SkCanvas::SaveFlags flags) {
+        int count = mCanvas->save();
+        recordPartialSave(flags);
+        return count;
+    }
+
+    int saveLayer(const SkRect* bounds, const SkPaint* paint,
+                            SkCanvas::SaveFlags flags) {
+        int count = mCanvas->saveLayer(bounds, paint,
+                static_cast<SkCanvas::SaveFlags>(flags | SkCanvas::kMatrixClip_SaveFlag));
+        recordPartialSave(flags);
+        return count;
+    }
+
+    int saveLayerAlpha(const SkRect* bounds, U8CPU alpha,
+                       SkCanvas::SaveFlags flags) {
+        int count = mCanvas->saveLayerAlpha(bounds, alpha,
+                static_cast<SkCanvas::SaveFlags>(flags | SkCanvas::kMatrixClip_SaveFlag));
+        recordPartialSave(flags);
+        return count;
+    }
+
+    void restore() {
+        const SaveRec* rec = (NULL == mSaveStack)
+                ? NULL
+                : static_cast<SaveRec*>(mSaveStack->back());
+        int currentSaveCount = mCanvas->getSaveCount() - 1;
+        SkASSERT(NULL == rec || currentSaveCount >= rec->saveCount);
+
+        if (NULL == rec || rec->saveCount != currentSaveCount) {
+            // Fast path - no record for this frame.
+            mCanvas->restore();
+            return;
+        }
+
+        bool preserveMatrix = !(rec->saveFlags & SkCanvas::kMatrix_SaveFlag);
+        bool preserveClip   = !(rec->saveFlags & SkCanvas::kClip_SaveFlag);
+
+        SkMatrix savedMatrix;
+        if (preserveMatrix) {
+            savedMatrix = mCanvas->getTotalMatrix();
+        }
+
+        SkTArray<SkClipStack::Element> savedClips;
+        if (preserveClip) {
+            saveClipsForFrame(savedClips, currentSaveCount);
+        }
+
+        mCanvas->restore();
+
+        if (preserveMatrix) {
+            mCanvas->setMatrix(savedMatrix);
+        }
+
+        if (preserveClip && !savedClips.empty()) {
+            applyClips(savedClips);
+        }
+
+        mSaveStack->pop_back();
+    }
+
+private:
+    void recordPartialSave(SkCanvas::SaveFlags flags) {
+        // A partial save is a save operation which doesn't capture the full canvas state.
+        // (either kMatrix_SaveFlags or kClip_SaveFlag is missing).
+
+        // Mask-out non canvas state bits.
+        flags = static_cast<SkCanvas::SaveFlags>(flags & SkCanvas::kMatrixClip_SaveFlag);
+
+        if (SkCanvas::kMatrixClip_SaveFlag == flags) {
+            // not a partial save.
+            return;
+        }
+
+        if (NULL == mSaveStack) {
+            mSaveStack = new SkDeque(sizeof(struct SaveRec), 8);
+        }
+
+        SaveRec* rec = static_cast<SaveRec*>(mSaveStack->push_back());
+        // Store the save counter in the SkClipStack domain.
+        // (0-based, equal to the number of save ops on the stack).
+        rec->saveCount = mCanvas->getSaveCount() - 1;
+        rec->saveFlags = flags;
+    }
+
+    void saveClipsForFrame(SkTArray<SkClipStack::Element>& clips,
+                           int frameSaveCount) {
+        SkClipStack::Iter clipIterator(*mCanvas->getClipStack(),
+                                       SkClipStack::Iter::kTop_IterStart);
+        while (const SkClipStack::Element* elem = clipIterator.next()) {
+            if (elem->getSaveCount() < frameSaveCount) {
+                // done with the current frame.
+                break;
+            }
+            SkASSERT(elem->getSaveCount() == frameSaveCount);
+            clips.push_back(*elem);
+        }
+    }
+
+    void applyClips(const SkTArray<SkClipStack::Element>& clips) {
+        ClipCopier clipCopier(mCanvas);
+
+        // The clip stack stores clips in device space.
+        SkMatrix origMatrix = mCanvas->getTotalMatrix();
+        mCanvas->resetMatrix();
+
+        // We pushed the clips in reverse order.
+        for (int i = clips.count() - 1; i >= 0; --i) {
+            clips[i].replay(&clipCopier);
+        }
+
+        mCanvas->setMatrix(origMatrix);
+    }
+
+    SkAutoTUnref<SkCanvas> mCanvas;
+    SkDeque* mSaveStack; // lazily allocated, tracks partial saves.
+};
+
 // Returns true if the SkCanvas's clip is non-empty.
 static jboolean hasNonEmptyClip(const SkCanvas& canvas) {
     bool emptyClip = canvas.isClipEmpty();
@@ -85,28 +221,35 @@
 
 class SkCanvasGlue {
 public:
-
-    static void finalizer(JNIEnv* env, jobject clazz, jlong canvasHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
-        canvas->unref();
+    // Get the native wrapper for a given handle.
+    static inline NativeCanvasWrapper* getNativeWrapper(jlong nativeHandle) {
+        SkASSERT(nativeHandle);
+        return reinterpret_cast<NativeCanvasWrapper*>(nativeHandle);
     }
 
-    static jlong initRaster(JNIEnv* env, jobject, jlong bitmapHandle) {
-        SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
+    // Get the SkCanvas for a given native handle.
+    static inline SkCanvas* getNativeCanvas(jlong nativeHandle) {
+        NativeCanvasWrapper* wrapper = getNativeWrapper(nativeHandle);
+        SkCanvas* canvas = wrapper->getCanvas();
+        SkASSERT(canvas);
+
+        return canvas;
+    }
+
+    // Construct an SkCanvas from the bitmap.
+    static SkCanvas* createCanvas(SkBitmap* bitmap) {
         if (bitmap) {
-            return reinterpret_cast<jlong>(new SkCanvas(*bitmap));
-        } else {
-            // Create an empty bitmap device to prevent callers from crashing
-            // if they attempt to draw into this canvas.
-            SkBitmap emptyBitmap;
-            return reinterpret_cast<jlong>(new SkCanvas(emptyBitmap));
+            return SkNEW_ARGS(SkCanvas, (*bitmap));
         }
+
+        // Create an empty bitmap device to prevent callers from crashing
+        // if they attempt to draw into this canvas.
+        SkBitmap emptyBitmap;
+        return new SkCanvas(emptyBitmap);
     }
 
-    static void copyCanvasState(JNIEnv* env, jobject clazz,
-                                jlong srcCanvasHandle, jlong dstCanvasHandle) {
-        SkCanvas* srcCanvas = reinterpret_cast<SkCanvas*>(srcCanvasHandle);
-        SkCanvas* dstCanvas = reinterpret_cast<SkCanvas*>(dstCanvasHandle);
+    // Copy the canvas matrix & clip state.
+    static void copyCanvasState(SkCanvas* srcCanvas, SkCanvas* dstCanvas) {
         if (srcCanvas && dstCanvas) {
             dstCanvas->setMatrix(srcCanvas->getTotalMatrix());
             if (NULL != srcCanvas->getDevice() && NULL != dstCanvas->getDevice()) {
@@ -116,10 +259,44 @@
         }
     }
 
+    // Native JNI handlers
+    static void finalizer(JNIEnv* env, jobject clazz, jlong nativeHandle) {
+        NativeCanvasWrapper* wrapper = reinterpret_cast<NativeCanvasWrapper*>(nativeHandle);
+        delete wrapper;
+    }
+
+    // Native wrapper constructor used by Canvas(Bitmap)
+    static jlong initRaster(JNIEnv* env, jobject, jlong bitmapHandle) {
+        // No check - 0 is a valid bitmapHandle.
+        SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
+        SkCanvas* canvas = createCanvas(bitmap);
+
+        return reinterpret_cast<jlong>(new NativeCanvasWrapper(canvas));
+    }
+
+    // Native wrapper constructor used by Canvas(native_canvas)
+    static jlong initCanvas(JNIEnv* env, jobject, jlong canvasHandle) {
+        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        return reinterpret_cast<jlong>(new NativeCanvasWrapper(canvas));
+    }
+
+    // Set the given bitmap as the new draw target (wrapped in a new SkCanvas),
+    // optionally copying canvas matrix & clip state.
+    static void setBitmap(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
+                          jboolean copyState) {
+        NativeCanvasWrapper* wrapper = reinterpret_cast<NativeCanvasWrapper*>(canvasHandle);
+        SkCanvas* newCanvas = createCanvas(reinterpret_cast<SkBitmap*>(bitmapHandle));
+        NPE_CHECK_RETURN_VOID(env, newCanvas);
+
+        if (copyState == JNI_TRUE) {
+            copyCanvasState(wrapper->getCanvas(), newCanvas);
+        }
+
+        // setCanvas() unrefs the old canvas.
+        wrapper->setCanvas(newCanvas);
+    }
 
     static void freeCaches(JNIEnv* env, jobject) {
-        // these are called in no particular order
-        SkImageRef_GlobalPool::SetRAMUsed(0);
         SkGraphics::PurgeFontCache();
     }
 
@@ -127,146 +304,107 @@
         TextLayoutEngine::getInstance().purgeCaches();
     }
 
-    static jboolean isOpaque(JNIEnv* env, jobject jcanvas) {
-        NPE_CHECK_RETURN_ZERO(env, jcanvas);
-        SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas);
+    static jboolean isOpaque(JNIEnv*, jobject, jlong canvasHandle) {
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         bool result = canvas->getDevice()->accessBitmap(false).isOpaque();
         return result ? JNI_TRUE : JNI_FALSE;
     }
 
-    static jint getWidth(JNIEnv* env, jobject jcanvas) {
-        NPE_CHECK_RETURN_ZERO(env, jcanvas);
-        SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas);
+    static jint getWidth(JNIEnv*, jobject, jlong canvasHandle) {
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         int width = canvas->getDevice()->accessBitmap(false).width();
         return static_cast<jint>(width);
     }
 
-    static jint getHeight(JNIEnv* env, jobject jcanvas) {
-        NPE_CHECK_RETURN_ZERO(env, jcanvas);
-        SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas);
+    static jint getHeight(JNIEnv*, jobject, jlong canvasHandle) {
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         int height = canvas->getDevice()->accessBitmap(false).height();
         return static_cast<jint>(height);
     }
 
-    static jint saveAll(JNIEnv* env, jobject jcanvas) {
-        NPE_CHECK_RETURN_ZERO(env, jcanvas);
-        int result = GraphicsJNI::getNativeCanvas(env, jcanvas)->save();
-        return static_cast<jint>(result);
-    }
-
-    static jint save(JNIEnv* env, jobject jcanvas, jint flagsHandle) {
+    static jint save(JNIEnv*, jobject, jlong canvasHandle, jint flagsHandle) {
+        NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle);
         SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
-        NPE_CHECK_RETURN_ZERO(env, jcanvas);
-        int result = GraphicsJNI::getNativeCanvas(env, jcanvas)->save(flags);
-        return static_cast<jint>(result);
+        return static_cast<jint>(wrapper->save(flags));
     }
 
-    static jint saveLayer(JNIEnv* env, jobject, jlong canvasHandle, jobject bounds,
-                         jlong paintHandle, jint flags) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+    static jint saveLayer(JNIEnv* env, jobject, jlong canvasHandle,
+                          jfloat l, jfloat t, jfloat r, jfloat b,
+                          jlong paintHandle, jint flagsHandle) {
+        NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle);
         SkPaint* paint  = reinterpret_cast<SkPaint*>(paintHandle);
-        SkRect* bounds_ = NULL;
-        SkRect  storage;
-        if (bounds != NULL) {
-            GraphicsJNI::jrectf_to_rect(env, bounds, &storage);
-            bounds_ = &storage;
-        }
-        return canvas->saveLayer(bounds_, paint, static_cast<SkCanvas::SaveFlags>(flags));
-    }
-
-    static jint saveLayer4F(JNIEnv* env, jobject, jlong canvasHandle,
-                           jfloat l, jfloat t, jfloat r, jfloat b,
-                           jlong paintHandle, jint flags) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
-        SkPaint* paint  = reinterpret_cast<SkPaint*>(paintHandle);
+        SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
         SkRect bounds;
         bounds.set(l, t, r, b);
-        int result = canvas->saveLayer(&bounds, paint,
-                                      static_cast<SkCanvas::SaveFlags>(flags));
-        return static_cast<jint>(result);
+        return static_cast<jint>(wrapper->saveLayer(&bounds, paint, flags));
     }
 
     static jint saveLayerAlpha(JNIEnv* env, jobject, jlong canvasHandle,
-                              jobject bounds, jint alpha, jint flags) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
-        SkRect* bounds_ = NULL;
-        SkRect  storage;
-        if (bounds != NULL) {
-            GraphicsJNI::jrectf_to_rect(env, bounds, &storage);
-            bounds_ = &storage;
-        }
-        int result = canvas->saveLayerAlpha(bounds_, alpha,
-                                      static_cast<SkCanvas::SaveFlags>(flags));
-        return static_cast<jint>(result);
-    }
-
-    static jint saveLayerAlpha4F(JNIEnv* env, jobject, jlong canvasHandle,
-                                jfloat l, jfloat t, jfloat r, jfloat b,
-                                jint alpha, jint flags) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+                               jfloat l, jfloat t, jfloat r, jfloat b,
+                               jint alpha, jint flagsHandle) {
+        NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle);
+        SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
         SkRect  bounds;
         bounds.set(l, t, r, b);
-        int result = canvas->saveLayerAlpha(&bounds, alpha,
-                                      static_cast<SkCanvas::SaveFlags>(flags));
-        return static_cast<jint>(result);
+        return static_cast<jint>(wrapper->saveLayerAlpha(&bounds, alpha, flags));
     }
 
-    static void restore(JNIEnv* env, jobject jcanvas) {
-        NPE_CHECK_RETURN_VOID(env, jcanvas);
-        SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas);
-        if (canvas->getSaveCount() <= 1) {  // cannot restore anymore
+    static void restore(JNIEnv* env, jobject, jlong canvasHandle) {
+        NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle);
+        if (wrapper->getCanvas()->getSaveCount() <= 1) {  // cannot restore anymore
             doThrowISE(env, "Underflow in restore");
             return;
         }
-        canvas->restore();
+        wrapper->restore();
     }
 
-    static jint getSaveCount(JNIEnv* env, jobject jcanvas) {
-        NPE_CHECK_RETURN_ZERO(env, jcanvas);
-        int result = GraphicsJNI::getNativeCanvas(env, jcanvas)->getSaveCount();
-        return static_cast<jint>(result);
+    static jint getSaveCount(JNIEnv*, jobject, jlong canvasHandle) {
+        return static_cast<jint>(getNativeCanvas(canvasHandle)->getSaveCount());
     }
 
-    static void restoreToCount(JNIEnv* env, jobject jcanvas, jint restoreCount) {
-        NPE_CHECK_RETURN_VOID(env, jcanvas);
-        SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas);
+    static void restoreToCount(JNIEnv* env, jobject, jlong canvasHandle,
+                               jint restoreCount) {
+        NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle);
         if (restoreCount < 1) {
             doThrowIAE(env, "Underflow in restoreToCount");
             return;
         }
-        canvas->restoreToCount(restoreCount);
+
+        while (wrapper->getCanvas()->getSaveCount() > restoreCount) {
+            wrapper->restore();
+        }
     }
 
-    static void translate(JNIEnv* env, jobject jcanvas, jfloat dx, jfloat dy) {
-        NPE_CHECK_RETURN_VOID(env, jcanvas);
-        (void)GraphicsJNI::getNativeCanvas(env, jcanvas)->translate(dx, dy);
+    static void translate(JNIEnv*, jobject, jlong canvasHandle,
+                          jfloat dx, jfloat dy) {
+        getNativeCanvas(canvasHandle)->translate(dx, dy);
     }
 
-    static void scale__FF(JNIEnv* env, jobject jcanvas, jfloat sx, jfloat sy) {
-        NPE_CHECK_RETURN_VOID(env, jcanvas);
-        (void)GraphicsJNI::getNativeCanvas(env, jcanvas)->scale(sx, sy);
+    static void scale__FF(JNIEnv*, jobject, jlong canvasHandle,
+                          jfloat sx, jfloat sy) {
+        getNativeCanvas(canvasHandle)->scale(sx, sy);
     }
 
-    static void rotate__F(JNIEnv* env, jobject jcanvas, jfloat degrees) {
-        NPE_CHECK_RETURN_VOID(env, jcanvas);
-        (void)GraphicsJNI::getNativeCanvas(env, jcanvas)->rotate(degrees);
+    static void rotate__F(JNIEnv*, jobject, jlong canvasHandle,
+                          jfloat degrees) {
+        getNativeCanvas(canvasHandle)->rotate(degrees);
     }
 
-    static void skew__FF(JNIEnv* env, jobject jcanvas, jfloat sx, jfloat sy) {
-        NPE_CHECK_RETURN_VOID(env, jcanvas);
-        (void)GraphicsJNI::getNativeCanvas(env, jcanvas)->skew(sx, sy);
+    static void skew__FF(JNIEnv*, jobject, jlong canvasHandle,
+                         jfloat sx, jfloat sy) {
+        getNativeCanvas(canvasHandle)->skew(sx, sy);
     }
 
     static void concat(JNIEnv* env, jobject, jlong canvasHandle,
                        jlong matrixHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
         canvas->concat(*matrix);
     }
 
     static void setMatrix(JNIEnv* env, jobject, jlong canvasHandle,
                           jlong matrixHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
         if (NULL == matrix) {
             canvas->resetMatrix();
@@ -275,59 +413,19 @@
         }
     }
 
-    static jboolean clipRect_FFFF(JNIEnv* env, jobject jcanvas, jfloat left,
-                                  jfloat top, jfloat right, jfloat bottom) {
-        NPE_CHECK_RETURN_ZERO(env, jcanvas);
+    static jboolean clipRect(JNIEnv*, jobject, jlong canvasHandle,
+                                  jfloat left, jfloat top, jfloat right,
+                                  jfloat bottom, jint op) {
         SkRect  r;
         r.set(left, top, right, bottom);
-        SkCanvas* c = GraphicsJNI::getNativeCanvas(env, jcanvas);
-        c->clipRect(r);
+        SkCanvas* c = getNativeCanvas(canvasHandle);
+        c->clipRect(r, static_cast<SkRegion::Op>(op));
         return hasNonEmptyClip(*c);
     }
 
-    static jboolean clipRect_IIII(JNIEnv* env, jobject jcanvas, jint left,
-                                  jint top, jint right, jint bottom) {
-        NPE_CHECK_RETURN_ZERO(env, jcanvas);
-        SkRect  r;
-        r.set(SkIntToScalar(left), SkIntToScalar(top),
-              SkIntToScalar(right), SkIntToScalar(bottom));
-        SkCanvas* c = GraphicsJNI::getNativeCanvas(env, jcanvas);
-        c->clipRect(r);
-        return hasNonEmptyClip(*c);
-    }
-
-    static jboolean clipRect_RectF(JNIEnv* env, jobject jcanvas, jobject rectf) {
-        NPE_CHECK_RETURN_ZERO(env, jcanvas);
-        NPE_CHECK_RETURN_ZERO(env, rectf);
-        SkCanvas* c = GraphicsJNI::getNativeCanvas(env, jcanvas);
-        SkRect tmp;
-        c->clipRect(*GraphicsJNI::jrectf_to_rect(env, rectf, &tmp));
-        return hasNonEmptyClip(*c);
-    }
-
-    static jboolean clipRect_Rect(JNIEnv* env, jobject jcanvas, jobject rect) {
-        NPE_CHECK_RETURN_ZERO(env, jcanvas);
-        NPE_CHECK_RETURN_ZERO(env, rect);
-        SkCanvas* c = GraphicsJNI::getNativeCanvas(env, jcanvas);
-        SkRect tmp;
-        c->clipRect(*GraphicsJNI::jrect_to_rect(env, rect, &tmp));
-        return hasNonEmptyClip(*c);
-
-    }
-
-    static jboolean clipRect(JNIEnv* env, jobject, jlong canvasHandle,
-                             jfloat left, jfloat top, jfloat right, jfloat bottom,
-                             jint op) {
-        SkRect rect;
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
-        rect.set(left, top, right, bottom);
-        canvas->clipRect(rect, static_cast<SkRegion::Op>(op));
-        return hasNonEmptyClip(*canvas);
-    }
-
     static jboolean clipPath(JNIEnv* env, jobject, jlong canvasHandle,
                              jlong pathHandle, jint op) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         canvas->clipPath(*reinterpret_cast<SkPath*>(pathHandle),
                 static_cast<SkRegion::Op>(op));
         return hasNonEmptyClip(*canvas);
@@ -335,30 +433,30 @@
 
     static jboolean clipRegion(JNIEnv* env, jobject, jlong canvasHandle,
                                jlong deviceRgnHandle, jint op) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkRegion* deviceRgn = reinterpret_cast<SkRegion*>(deviceRgnHandle);
-        canvas->clipRegion(*deviceRgn, static_cast<SkRegion::Op>(op));
+        SkPath rgnPath;
+        if (deviceRgn->getBoundaryPath(&rgnPath)) {
+            // The region is specified in device space.
+            SkMatrix savedMatrix = canvas->getTotalMatrix();
+            canvas->resetMatrix();
+            canvas->clipPath(rgnPath, static_cast<SkRegion::Op>(op));
+            canvas->setMatrix(savedMatrix);
+        } else {
+            canvas->clipRect(SkRect::MakeEmpty(), static_cast<SkRegion::Op>(op));
+        }
         return hasNonEmptyClip(*canvas);
     }
 
     static void setDrawFilter(JNIEnv* env, jobject, jlong canvasHandle,
                               jlong filterHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         canvas->setDrawFilter(reinterpret_cast<SkDrawFilter*>(filterHandle));
     }
 
-    static jboolean quickReject__RectF(JNIEnv* env, jobject, jlong canvasHandle,
-                                        jobject rect) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
-        SkRect rect_;
-        GraphicsJNI::jrectf_to_rect(env, rect, &rect_);
-        bool result = canvas->quickReject(rect_);
-        return result ? JNI_TRUE : JNI_FALSE;
-    }
-
     static jboolean quickReject__Path(JNIEnv* env, jobject, jlong canvasHandle,
                                        jlong pathHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         bool result = canvas->quickReject(*reinterpret_cast<SkPath*>(pathHandle));
         return result ? JNI_TRUE : JNI_FALSE;
     }
@@ -366,7 +464,7 @@
     static jboolean quickReject__FFFF(JNIEnv* env, jobject, jlong canvasHandle,
                                        jfloat left, jfloat top, jfloat right,
                                        jfloat bottom) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkRect r;
         r.set(left, top, right, bottom);
         bool result = canvas->quickReject(r);
@@ -375,45 +473,43 @@
 
     static void drawRGB(JNIEnv* env, jobject, jlong canvasHandle,
                         jint r, jint g, jint b) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         canvas->drawARGB(0xFF, r, g, b);
     }
 
     static void drawARGB(JNIEnv* env, jobject, jlong canvasHandle,
                          jint a, jint r, jint g, jint b) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         canvas->drawARGB(a, r, g, b);
     }
 
     static void drawColor__I(JNIEnv* env, jobject, jlong canvasHandle,
                              jint color) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         canvas->drawColor(color);
     }
 
     static void drawColor__II(JNIEnv* env, jobject, jlong canvasHandle,
                               jint color, jint modeHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPorterDuff::Mode mode = static_cast<SkPorterDuff::Mode>(modeHandle);
         canvas->drawColor(color, SkPorterDuff::ToXfermodeMode(mode));
     }
 
     static void drawPaint(JNIEnv* env, jobject, jlong canvasHandle,
                           jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         canvas->drawPaint(*paint);
     }
 
-    static void doPoints(JNIEnv* env, jobject jcanvas, jfloatArray jptsArray,
-                         jint offset, jint count, jobject jpaint,
-                         jint modeHandle) {
-        NPE_CHECK_RETURN_VOID(env, jcanvas);
+    static void doPoints(JNIEnv* env, jlong canvasHandle,
+                         jfloatArray jptsArray, jint offset, jint count,
+                         jlong paintHandle, jint modeHandle) {
         NPE_CHECK_RETURN_VOID(env, jptsArray);
-        NPE_CHECK_RETURN_VOID(env, jpaint);
         SkCanvas::PointMode mode = static_cast<SkCanvas::PointMode>(modeHandle);
-        SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas);
-        const SkPaint& paint = *GraphicsJNI::getNativePaint(env, jpaint);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
+        const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
 
         AutoJavaFloatArray autoPts(env, jptsArray);
         float* floats = autoPts.ptr();
@@ -433,59 +529,49 @@
             pts[i].set(src[0], src[1]);
             src += 2;
         }
-        canvas->drawPoints(mode, count, pts, paint);
+        canvas->drawPoints(mode, count, pts, *paint);
     }
 
-    static void drawPoints(JNIEnv* env, jobject jcanvas, jfloatArray jptsArray,
-                           jint offset, jint count, jobject jpaint) {
-        doPoints(env, jcanvas, jptsArray, offset, count, jpaint,
+    static void drawPoints(JNIEnv* env, jobject, jlong canvasHandle,
+                           jfloatArray jptsArray, jint offset,
+                           jint count, jlong paintHandle) {
+        doPoints(env, canvasHandle, jptsArray, offset, count, paintHandle,
                  SkCanvas::kPoints_PointMode);
     }
 
-    static void drawLines(JNIEnv* env, jobject jcanvas, jfloatArray jptsArray,
-                           jint offset, jint count, jobject jpaint) {
-        doPoints(env, jcanvas, jptsArray, offset, count, jpaint,
+    static void drawLines(JNIEnv* env, jobject, jlong canvasHandle,
+                          jfloatArray jptsArray, jint offset, jint count,
+                          jlong paintHandle) {
+        doPoints(env, canvasHandle, jptsArray, offset, count, paintHandle,
                  SkCanvas::kLines_PointMode);
     }
 
-    static void drawPoint(JNIEnv* env, jobject jcanvas, jfloat x, jfloat y,
-                          jobject jpaint) {
-        NPE_CHECK_RETURN_VOID(env, jcanvas);
-        NPE_CHECK_RETURN_VOID(env, jpaint);
-        SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, jcanvas);
-        const SkPaint& paint = *GraphicsJNI::getNativePaint(env, jpaint);
-
-        canvas->drawPoint(x, y, paint);
+    static void drawPoint(JNIEnv*, jobject, jlong canvasHandle, jfloat x, jfloat y,
+                          jlong paintHandle) {
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
+        const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+        canvas->drawPoint(x, y, *paint);
     }
 
     static void drawLine__FFFFPaint(JNIEnv* env, jobject, jlong canvasHandle,
                                     jfloat startX, jfloat startY, jfloat stopX,
                                     jfloat stopY, jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         canvas->drawLine(startX, startY, stopX, stopY, *paint);
     }
 
-    static void drawRect__RectFPaint(JNIEnv* env, jobject, jlong canvasHandle,
-                                     jobject rect, jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
-        SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-        SkRect rect_;
-        GraphicsJNI::jrectf_to_rect(env, rect, &rect_);
-        canvas->drawRect(rect_, *paint);
-    }
-
     static void drawRect__FFFFPaint(JNIEnv* env, jobject, jlong canvasHandle,
                                     jfloat left, jfloat top, jfloat right,
                                     jfloat bottom, jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         canvas->drawRectCoords(left, top, right, bottom, *paint);
     }
 
     static void drawOval(JNIEnv* env, jobject, jlong canvasHandle, jobject joval,
                          jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         SkRect oval;
         GraphicsJNI::jrectf_to_rect(env, joval, &oval);
@@ -494,7 +580,7 @@
 
     static void drawCircle(JNIEnv* env, jobject, jlong canvasHandle, jfloat cx,
                            jfloat cy, jfloat radius, jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         canvas->drawCircle(cx, cy, radius, *paint);
     }
@@ -502,7 +588,7 @@
     static void drawArc(JNIEnv* env, jobject, jlong canvasHandle, jobject joval,
                         jfloat startAngle, jfloat sweepAngle,
                         jboolean useCenter, jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         SkRect oval;
         GraphicsJNI::jrectf_to_rect(env, joval, &oval);
@@ -512,7 +598,7 @@
     static void drawRoundRect(JNIEnv* env, jobject, jlong canvasHandle,
             jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat rx, jfloat ry,
             jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
         canvas->drawRoundRect(rect, rx, ry, *paint);
@@ -520,7 +606,7 @@
 
     static void drawPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle,
                          jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         canvas->drawPath(*path, *paint);
@@ -531,7 +617,7 @@
                                           jfloat left, jfloat top,
                                           jlong paintHandle, jint canvasDensity,
                                           jint screenDensity, jint bitmapDensity) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
 
@@ -591,7 +677,7 @@
                              jlong bitmapHandle, jobject srcIRect,
                              jobject dstRectF, jlong paintHandle,
                              jint screenDensity, jint bitmapDensity) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         SkRect      dst;
@@ -604,7 +690,7 @@
                              jlong bitmapHandle, jobject srcIRect,
                              jobject dstRect, jlong paintHandle,
                              jint screenDensity, jint bitmapDensity) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         SkRect      dst;
@@ -616,9 +702,8 @@
     static void drawBitmapArray(JNIEnv* env, jobject, jlong canvasHandle,
                                 jintArray jcolors, jint offset, jint stride,
                                 jfloat x, jfloat y, jint width, jint height,
-                                jboolean hasAlpha, jlong paintHandle)
-    {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+                                jboolean hasAlpha, jlong paintHandle) {
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         SkBitmap    bitmap;
         bitmap.setConfig(hasAlpha ? SkBitmap::kARGB_8888_Config :
@@ -638,7 +723,7 @@
     static void drawBitmapMatrix(JNIEnv* env, jobject, jlong canvasHandle,
                                  jlong bitmapHandle, jlong matrixHandle,
                                  jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
         const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
         const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
@@ -649,7 +734,7 @@
                           jlong bitmapHandle, jint meshWidth, jint meshHeight,
                           jfloatArray jverts, jint vertIndex, jintArray jcolors,
                           jint colorIndex, jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
         const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
 
@@ -759,7 +844,7 @@
                              jintArray jcolors, jint colorIndex,
                              jshortArray jindices, jint indexIndex,
                              jint indexCount, jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkCanvas::VertexMode mode = static_cast<SkCanvas::VertexMode>(modeHandle);
         const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
 
@@ -799,7 +884,7 @@
                                                jcharArray text, jint index, jint count,
                                                jfloat x, jfloat y, jint flags,
                                                jlong paintHandle, jlong typefaceHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
         jchar* textArray = env->GetCharArrayElements(text, NULL);
@@ -812,7 +897,7 @@
                                                    jint start, jint end,
                                                    jfloat x, jfloat y, jint flags,
                                                    jlong paintHandle, jlong typefaceHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
         const jchar* textArray = env->GetStringChars(text, NULL);
@@ -951,10 +1036,10 @@
     }
 
     static void drawTextRun___CIIIIFFIPaintTypeface(
-        JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index,
-        jint count, jint contextIndex, jint contextCount,
-        jfloat x, jfloat y, jint dirFlags, jlong paintHandle, jlong typefaceHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+            JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index,
+            jint count, jint contextIndex, jint contextCount,
+            jfloat x, jfloat y, jint dirFlags, jlong paintHandle, jlong typefaceHandle) {
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
 
@@ -965,10 +1050,10 @@
     }
 
     static void drawTextRun__StringIIIIFFIPaintTypeface(
-        JNIEnv* env, jobject obj, jlong canvasHandle, jstring text, jint start,
-        jint end, jint contextStart, jint contextEnd,
-        jfloat x, jfloat y, jint dirFlags, jlong paintHandle, jlong typefaceHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+            JNIEnv* env, jobject obj, jlong canvasHandle, jstring text, jint start,
+            jint end, jint contextStart, jint contextEnd,
+            jfloat x, jfloat y, jint dirFlags, jlong paintHandle, jlong typefaceHandle) {
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
 
@@ -983,7 +1068,7 @@
     static void drawPosText___CII_FPaint(JNIEnv* env, jobject, jlong canvasHandle,
                                          jcharArray text, jint index, jint count,
                                          jfloatArray pos, jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         jchar* textArray = text ? env->GetCharArrayElements(text, NULL) : NULL;
         jsize textCount = text ? env->GetArrayLength(text) : NULL;
@@ -1014,7 +1099,7 @@
                                            jlong canvasHandle, jstring text,
                                            jfloatArray pos,
                                            jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         const void* text_ = text ? env->GetStringChars(text, NULL) : NULL;
         int byteLength = text ? env->GetStringLength(text) : 0;
@@ -1044,7 +1129,7 @@
     static void drawTextOnPath___CIIPathFFPaint(JNIEnv* env, jobject,
             jlong canvasHandle, jcharArray text, jint index, jint count,
             jlong pathHandle, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
 
@@ -1057,7 +1142,7 @@
     static void drawTextOnPath__StringPathFFPaint(JNIEnv* env, jobject,
             jlong canvasHandle, jstring text, jlong pathHandle,
             jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
         SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         const jchar* text_ = env->GetStringChars(text, NULL);
@@ -1096,7 +1181,7 @@
 
     static jboolean getClipBounds(JNIEnv* env, jobject, jlong canvasHandle,
                                   jobject bounds) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkRect   r;
         SkIRect ir;
         bool result = getHardClipBounds(canvas, &r);
@@ -1112,7 +1197,7 @@
 
     static void getCTM(JNIEnv* env, jobject, jlong canvasHandle,
                        jlong matrixHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas = getNativeCanvas(canvasHandle);
         SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
         *matrix = canvas->getTotalMatrix();
     }
@@ -1120,35 +1205,24 @@
 
 static JNINativeMethod gCanvasMethods[] = {
     {"finalizer", "(J)V", (void*) SkCanvasGlue::finalizer},
-    {"initRaster","(J)J", (void*) SkCanvasGlue::initRaster},
-    {"copyNativeCanvasState","(JJ)V", (void*) SkCanvasGlue::copyCanvasState},
-    {"isOpaque","()Z", (void*) SkCanvasGlue::isOpaque},
-    {"getWidth","()I", (void*) SkCanvasGlue::getWidth},
-    {"getHeight","()I", (void*) SkCanvasGlue::getHeight},
-    {"save","()I", (void*) SkCanvasGlue::saveAll},
-    {"save","(I)I", (void*) SkCanvasGlue::save},
-    {"native_saveLayer","(JLandroid/graphics/RectF;JI)I",
-        (void*) SkCanvasGlue::saveLayer},
-    {"native_saveLayer","(JFFFFJI)I", (void*) SkCanvasGlue::saveLayer4F},
-    {"native_saveLayerAlpha","(JLandroid/graphics/RectF;II)I",
-        (void*) SkCanvasGlue::saveLayerAlpha},
-    {"native_saveLayerAlpha","(JFFFFII)I",
-        (void*) SkCanvasGlue::saveLayerAlpha4F},
-    {"restore","()V", (void*) SkCanvasGlue::restore},
-    {"getSaveCount","()I", (void*) SkCanvasGlue::getSaveCount},
-    {"restoreToCount","(I)V", (void*) SkCanvasGlue::restoreToCount},
-    {"translate","(FF)V", (void*) SkCanvasGlue::translate},
-    {"scale","(FF)V", (void*) SkCanvasGlue::scale__FF},
-    {"rotate","(F)V", (void*) SkCanvasGlue::rotate__F},
-    {"skew","(FF)V", (void*) SkCanvasGlue::skew__FF},
+    {"initRaster", "(J)J", (void*) SkCanvasGlue::initRaster},
+    {"initCanvas", "(J)J", (void*) SkCanvasGlue::initCanvas},
+    {"native_setBitmap", "(JJZ)V", (void*) SkCanvasGlue::setBitmap},
+    {"native_isOpaque","(J)Z", (void*) SkCanvasGlue::isOpaque},
+    {"native_getWidth","(J)I", (void*) SkCanvasGlue::getWidth},
+    {"native_getHeight","(J)I", (void*) SkCanvasGlue::getHeight},
+    {"native_save","(JI)I", (void*) SkCanvasGlue::save},
+    {"native_saveLayer","(JFFFFJI)I", (void*) SkCanvasGlue::saveLayer},
+    {"native_saveLayerAlpha","(JFFFFII)I", (void*) SkCanvasGlue::saveLayerAlpha},
+    {"native_restore","(J)V", (void*) SkCanvasGlue::restore},
+    {"native_getSaveCount","(J)I", (void*) SkCanvasGlue::getSaveCount},
+    {"native_restoreToCount","(JI)V", (void*) SkCanvasGlue::restoreToCount},
+    {"native_translate","(JFF)V", (void*) SkCanvasGlue::translate},
+    {"native_scale","(JFF)V", (void*) SkCanvasGlue::scale__FF},
+    {"native_rotate","(JF)V", (void*) SkCanvasGlue::rotate__F},
+    {"native_skew","(JFF)V", (void*) SkCanvasGlue::skew__FF},
     {"native_concat","(JJ)V", (void*) SkCanvasGlue::concat},
     {"native_setMatrix","(JJ)V", (void*) SkCanvasGlue::setMatrix},
-    {"clipRect","(FFFF)Z", (void*) SkCanvasGlue::clipRect_FFFF},
-    {"clipRect","(IIII)Z", (void*) SkCanvasGlue::clipRect_IIII},
-    {"clipRect","(Landroid/graphics/RectF;)Z",
-        (void*) SkCanvasGlue::clipRect_RectF},
-    {"clipRect","(Landroid/graphics/Rect;)Z",
-        (void*) SkCanvasGlue::clipRect_Rect},
     {"native_clipRect","(JFFFFI)Z", (void*) SkCanvasGlue::clipRect},
     {"native_clipPath","(JJI)Z", (void*) SkCanvasGlue::clipPath},
     {"native_clipRegion","(JJI)Z", (void*) SkCanvasGlue::clipRegion},
@@ -1156,8 +1230,6 @@
     {"native_getClipBounds","(JLandroid/graphics/Rect;)Z",
         (void*) SkCanvasGlue::getClipBounds},
     {"native_getCTM", "(JJ)V", (void*)SkCanvasGlue::getCTM},
-    {"native_quickReject","(JLandroid/graphics/RectF;)Z",
-        (void*) SkCanvasGlue::quickReject__RectF},
     {"native_quickReject","(JJ)Z", (void*) SkCanvasGlue::quickReject__Path},
     {"native_quickReject","(JFFFF)Z", (void*)SkCanvasGlue::quickReject__FFFF},
     {"native_drawRGB","(JIII)V", (void*) SkCanvasGlue::drawRGB},
@@ -1165,15 +1237,10 @@
     {"native_drawColor","(JI)V", (void*) SkCanvasGlue::drawColor__I},
     {"native_drawColor","(JII)V", (void*) SkCanvasGlue::drawColor__II},
     {"native_drawPaint","(JJ)V", (void*) SkCanvasGlue::drawPaint},
-    {"drawPoint", "(FFLandroid/graphics/Paint;)V",
-    (void*) SkCanvasGlue::drawPoint},
-    {"drawPoints", "([FIILandroid/graphics/Paint;)V",
-        (void*) SkCanvasGlue::drawPoints},
-    {"drawLines", "([FIILandroid/graphics/Paint;)V",
-        (void*) SkCanvasGlue::drawLines},
+    {"native_drawPoint", "(JFFJ)V", (void*) SkCanvasGlue::drawPoint},
+    {"native_drawPoints", "(J[FIIJ)V", (void*) SkCanvasGlue::drawPoints},
+    {"native_drawLines", "(J[FIIJ)V", (void*) SkCanvasGlue::drawLines},
     {"native_drawLine","(JFFFFJ)V", (void*) SkCanvasGlue::drawLine__FFFFPaint},
-    {"native_drawRect","(JLandroid/graphics/RectF;J)V",
-        (void*) SkCanvasGlue::drawRect__RectFPaint},
     {"native_drawRect","(JFFFFJ)V", (void*) SkCanvasGlue::drawRect__FFFFPaint},
     {"native_drawOval","(JLandroid/graphics/RectF;J)V",
         (void*) SkCanvasGlue::drawOval},
@@ -1236,4 +1303,11 @@
     return result;
 }
 
+} // namespace android
+
+// GraphicsJNI helper for external clients.
+// We keep the implementation here to avoid exposing NativeCanvasWrapper
+// externally.
+SkCanvas* GraphicsJNI::getNativeCanvas(jlong nativeHandle) {
+    return android::SkCanvasGlue::getNativeCanvas(nativeHandle);
 }
diff --git a/core/jni/android/graphics/DrawFilter.cpp b/core/jni/android/graphics/DrawFilter.cpp
index fbfa2ec..3275875 100644
--- a/core/jni/android/graphics/DrawFilter.cpp
+++ b/core/jni/android/graphics/DrawFilter.cpp
@@ -30,6 +30,35 @@
 
 namespace android {
 
+// Custom version of SkPaintFlagsDrawFilter that also calls setFilterLevel.
+class CompatFlagsDrawFilter : public SkPaintFlagsDrawFilter {
+public:
+    CompatFlagsDrawFilter(uint32_t clearFlags, uint32_t setFlags,
+            SkPaint::FilterLevel desiredLevel)
+    : SkPaintFlagsDrawFilter(clearFlags, setFlags)
+    , fDesiredLevel(desiredLevel) {
+    }
+
+    virtual bool filter(SkPaint* paint, Type type) {
+        SkPaintFlagsDrawFilter::filter(paint, type);
+        paint->setFilterLevel(fDesiredLevel);
+        return true;
+    }
+
+private:
+    const SkPaint::FilterLevel fDesiredLevel;
+};
+
+// Returns whether flags contains FILTER_BITMAP_FLAG. If flags does, remove it.
+static inline bool hadFiltering(jint& flags) {
+    // Equivalent to the Java Paint's FILTER_BITMAP_FLAG.
+    static const uint32_t sFilterBitmapFlag = 0x02;
+
+    const bool result = (flags & sFilterBitmapFlag) != 0;
+    flags &= ~sFilterBitmapFlag;
+    return result;
+}
+
 class SkDrawFilterGlue {
 public:
 
@@ -40,12 +69,25 @@
 
     static jlong CreatePaintFlagsDF(JNIEnv* env, jobject clazz,
                                     jint clearFlags, jint setFlags) {
-        // trim off any out-of-range bits
-        clearFlags &= SkPaint::kAllFlags;
-        setFlags &= SkPaint::kAllFlags;
-
         if (clearFlags | setFlags) {
-            SkDrawFilter* filter = new SkPaintFlagsDrawFilter(clearFlags, setFlags);
+            // Mask both groups of flags to remove FILTER_BITMAP_FLAG, which no
+            // longer has a Skia equivalent flag (instead it corresponds to
+            // calling setFilterLevel), and keep track of which group(s), if
+            // any, had the flag set.
+            const bool turnFilteringOn = hadFiltering(setFlags);
+            const bool turnFilteringOff = hadFiltering(clearFlags);
+
+            SkDrawFilter* filter;
+            if (turnFilteringOn) {
+                // Turning filtering on overrides turning it off.
+                filter = new CompatFlagsDrawFilter(clearFlags, setFlags,
+                        SkPaint::kLow_FilterLevel);
+            } else if (turnFilteringOff) {
+                filter = new CompatFlagsDrawFilter(clearFlags, setFlags,
+                        SkPaint::kNone_FilterLevel);
+            } else {
+                filter = new SkPaintFlagsDrawFilter(clearFlags, setFlags);
+            }
             return reinterpret_cast<jlong>(filter);
         } else {
             return NULL;
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index dce185d..a4337ccc 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -3,6 +3,7 @@
 #include "jni.h"
 #include "JNIHelp.h"
 #include "GraphicsJNI.h"
+#include "AndroidPicture.h"
 
 #include "SkCanvas.h"
 #include "SkDevice.h"
@@ -320,7 +321,7 @@
     SkASSERT(canvas);
     SkASSERT(env->IsInstanceOf(canvas, gCanvas_class));
     jlong canvasHandle = env->GetLongField(canvas, gCanvas_nativeInstanceID);
-    SkCanvas* c = reinterpret_cast<SkCanvas*>(canvasHandle);
+    SkCanvas* c = getNativeCanvas(canvasHandle);
     SkASSERT(c);
     return c;
 }
@@ -345,13 +346,13 @@
     return p;
 }
 
-SkPicture* GraphicsJNI::getNativePicture(JNIEnv* env, jobject picture)
+AndroidPicture* GraphicsJNI::getNativePicture(JNIEnv* env, jobject picture)
 {
     SkASSERT(env);
     SkASSERT(picture);
     SkASSERT(env->IsInstanceOf(picture, gPicture_class));
     jlong pictureHandle = env->GetLongField(picture, gPicture_nativeInstanceID);
-    SkPicture* p = reinterpret_cast<SkPicture*>(pictureHandle);
+    AndroidPicture* p = reinterpret_cast<AndroidPicture*>(pictureHandle);
     SkASSERT(p);
     return p;
 }
@@ -698,7 +699,7 @@
                                                      "nativeInt", "I");
 
     gCanvas_class = make_globalref(env, "android/graphics/Canvas");
-    gCanvas_nativeInstanceID = getFieldIDCheck(env, gCanvas_class, "mNativeCanvas", "J");
+    gCanvas_nativeInstanceID = getFieldIDCheck(env, gCanvas_class, "mNativeCanvasWrapper", "J");
 
     gPaint_class = make_globalref(env, "android/graphics/Paint");
     gPaint_nativeInstanceID = getFieldIDCheck(env, gPaint_class, "mNativePaint", "J");
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index db7b6d9..2e2f920 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -14,7 +14,7 @@
 class SkBitmapRegionDecoder;
 class SkCanvas;
 class SkPaint;
-class SkPicture;
+class AndroidPicture;
 
 class GraphicsJNI {
 public:
@@ -45,11 +45,12 @@
     static SkPoint* jpointf_to_point(JNIEnv*, jobject jpointf, SkPoint* point);
     static void point_to_jpointf(const SkPoint& point, JNIEnv*, jobject jpointf);
 
+    static SkCanvas* getNativeCanvas(jlong nativeHandle);
     static SkCanvas* getNativeCanvas(JNIEnv*, jobject canvas);
     static SkPaint*  getNativePaint(JNIEnv*, jobject paint);
     static android::TypefaceImpl* getNativeTypeface(JNIEnv*, jobject paint);
     static SkBitmap* getNativeBitmap(JNIEnv*, jobject bitmap);
-    static SkPicture* getNativePicture(JNIEnv*, jobject picture);
+    static AndroidPicture* getNativePicture(JNIEnv*, jobject picture);
     static SkRegion* getNativeRegion(JNIEnv*, jobject region);
 
     // Given the 'native' long held by the Rasterizer.java object, return a
diff --git a/core/jni/android/graphics/MaskFilter.cpp b/core/jni/android/graphics/MaskFilter.cpp
index 2113330..b394905 100644
--- a/core/jni/android/graphics/MaskFilter.cpp
+++ b/core/jni/android/graphics/MaskFilter.cpp
@@ -21,8 +21,7 @@
 
     static jlong createBlur(JNIEnv* env, jobject, jfloat radius, jint blurStyle) {
         SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius);
-        SkMaskFilter* filter = SkBlurMaskFilter::Create(
-                (SkBlurMaskFilter::BlurStyle)blurStyle, sigma);
+        SkMaskFilter* filter = SkBlurMaskFilter::Create((SkBlurStyle)blurStyle, sigma);
         ThrowIAE_IfNull(env, filter);
         return reinterpret_cast<jlong>(filter);
     }
diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp
index 855d267..ab5bdb0 100644
--- a/core/jni/android/graphics/NinePatch.cpp
+++ b/core/jni/android/graphics/NinePatch.cpp
@@ -119,7 +119,7 @@
     static void drawF(JNIEnv* env, jobject, jlong canvasHandle, jobject boundsRectF,
             jlong bitmapHandle, jlong chunkHandle, jlong paintHandle,
             jint destDensity, jint srcDensity) {
-        SkCanvas* canvas       = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas       = GraphicsJNI::getNativeCanvas(canvasHandle);
         const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
         Res_png_9patch* chunk  = reinterpret_cast<Res_png_9patch*>(chunkHandle);
         const SkPaint* paint   = reinterpret_cast<SkPaint*>(paintHandle);
@@ -138,7 +138,7 @@
     static void drawI(JNIEnv* env, jobject, jlong canvasHandle, jobject boundsRect,
             jlong bitmapHandle, jlong chunkHandle, jlong paintHandle,
             jint destDensity, jint srcDensity) {
-        SkCanvas* canvas       = reinterpret_cast<SkCanvas*>(canvasHandle);
+        SkCanvas* canvas       = GraphicsJNI::getNativeCanvas(canvasHandle);
         const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
         Res_png_9patch* chunk  = reinterpret_cast<Res_png_9patch*>(chunkHandle);
         const SkPaint* paint   = reinterpret_cast<SkPaint*>(paintHandle);
diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
index bac8ef7..0683f73 100644
--- a/core/jni/android/graphics/Picture.cpp
+++ b/core/jni/android/graphics/Picture.cpp
@@ -17,9 +17,9 @@
 #include "jni.h"
 #include "GraphicsJNI.h"
 #include <android_runtime/AndroidRuntime.h>
+#include "AndroidPicture.h"
 
 #include "SkCanvas.h"
-#include "SkPicture.h"
 #include "SkStream.h"
 #include "SkTemplates.h"
 #include "CreateJavaOutputStreamAdaptor.h"
@@ -29,45 +29,41 @@
 class SkPictureGlue {
 public:
     static jlong newPicture(JNIEnv* env, jobject, jlong srcHandle) {
-        const SkPicture* src = reinterpret_cast<SkPicture*>(srcHandle);
-        if (src) {
-            return reinterpret_cast<jlong>(new SkPicture(*src));
-        } else {
-            return reinterpret_cast<jlong>(new SkPicture);
-        }
+        const AndroidPicture* src = reinterpret_cast<AndroidPicture*>(srcHandle);
+        return reinterpret_cast<jlong>(new AndroidPicture(src));
     }
 
     static jlong deserialize(JNIEnv* env, jobject, jobject jstream,
                              jbyteArray jstorage) {
-        SkPicture* picture = NULL;
+        AndroidPicture* picture = NULL;
         SkStream* strm = CreateJavaInputStreamAdaptor(env, jstream, jstorage);
         if (strm) {
-            picture = SkPicture::CreateFromStream(strm);
+            picture = AndroidPicture::CreateFromStream(strm);
             delete strm;
         }
         return reinterpret_cast<jlong>(picture);
     }
 
     static void killPicture(JNIEnv* env, jobject, jlong pictureHandle) {
-        SkPicture* picture = reinterpret_cast<SkPicture*>(pictureHandle);
+        AndroidPicture* picture = reinterpret_cast<AndroidPicture*>(pictureHandle);
         SkASSERT(picture);
-        picture->unref();
+        delete picture;
     }
 
     static void draw(JNIEnv* env, jobject, jlong canvasHandle,
                             jlong pictureHandle) {
-        SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
-        SkPicture* picture = reinterpret_cast<SkPicture*>(pictureHandle);
+        SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle);
+        AndroidPicture* picture = reinterpret_cast<AndroidPicture*>(pictureHandle);
         SkASSERT(canvas);
         SkASSERT(picture);
         picture->draw(canvas);
     }
 
     static jboolean serialize(JNIEnv* env, jobject, jlong pictureHandle,
-                          jobject jstream, jbyteArray jstorage) {
-        SkPicture* picture = reinterpret_cast<SkPicture*>(pictureHandle);
+                              jobject jstream, jbyteArray jstorage) {
+        AndroidPicture* picture = reinterpret_cast<AndroidPicture*>(pictureHandle);
         SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
-        
+
         if (NULL != strm) {
             picture->serialize(strm);
             delete strm;
@@ -78,19 +74,21 @@
 
     static jint getWidth(JNIEnv* env, jobject jpic) {
         NPE_CHECK_RETURN_ZERO(env, jpic);
-        int width = GraphicsJNI::getNativePicture(env, jpic)->width();
+        AndroidPicture* pict = GraphicsJNI::getNativePicture(env, jpic);
+        int width = pict->width();
         return static_cast<jint>(width);
     }
 
     static jint getHeight(JNIEnv* env, jobject jpic) {
         NPE_CHECK_RETURN_ZERO(env, jpic);
-        int height = GraphicsJNI::getNativePicture(env, jpic)->height();
+        AndroidPicture* pict = GraphicsJNI::getNativePicture(env, jpic);
+        int height = pict->height();
         return static_cast<jint>(height);
     }
 
     static jlong beginRecording(JNIEnv* env, jobject, jlong pictHandle,
-                                    jint w, jint h) {
-        SkPicture* pict = reinterpret_cast<SkPicture*>(pictHandle);
+                                jint w, jint h) {
+        AndroidPicture* pict = reinterpret_cast<AndroidPicture*>(pictHandle);
         // beginRecording does not ref its return value, it just returns it.
         SkCanvas* canvas = pict->beginRecording(w, h);
         // the java side will wrap this guy in a Canvas.java, which will call
@@ -101,7 +99,7 @@
     }
 
     static void endRecording(JNIEnv* env, jobject, jlong pictHandle) {
-        SkPicture* pict = reinterpret_cast<SkPicture*>(pictHandle);
+        AndroidPicture* pict = reinterpret_cast<AndroidPicture*>(pictHandle);
         pict->endRecording();
     }
 };
diff --git a/core/jni/android/graphics/pdf/PdfDocument.cpp b/core/jni/android/graphics/pdf/PdfDocument.cpp
index d54aaa8..3812c27 100644
--- a/core/jni/android/graphics/pdf/PdfDocument.cpp
+++ b/core/jni/android/graphics/pdf/PdfDocument.cpp
@@ -24,6 +24,7 @@
 #include "SkCanvas.h"
 #include "SkDocument.h"
 #include "SkPicture.h"
+#include "SkPictureRecorder.h"
 #include "SkStream.h"
 #include "SkRect.h"
 
@@ -32,15 +33,22 @@
 struct PageRecord {
 
     PageRecord(int width, int height, const SkRect& contentRect)
-            : mPicture(new SkPicture()), mWidth(width), mHeight(height) {
+            : mPictureRecorder(new SkPictureRecorder())
+            , mPicture(NULL)
+            , mWidth(width)
+            , mHeight(height) {
         mContentRect = contentRect;
     }
 
     ~PageRecord() {
-        mPicture->unref();
+        delete mPictureRecorder;
+        if (NULL != mPicture) {
+            mPicture->unref();
+        }
     }
 
-    SkPicture* const mPicture;
+    SkPictureRecorder* mPictureRecorder;
+    SkPicture* mPicture;
     const int mWidth;
     const int mHeight;
     SkRect mContentRect;
@@ -62,8 +70,8 @@
         mPages.push_back(page);
         mCurrentPage = page;
 
-        SkCanvas* canvas = page->mPicture->beginRecording(
-                contentRect.width(), contentRect.height(), 0);
+        SkCanvas* canvas = page->mPictureRecorder->beginRecording(
+                contentRect.width(), contentRect.height(), NULL, 0);
 
         // We pass this canvas to Java where it is used to construct
         // a Java Canvas object which dereferences the pointer when it
@@ -75,7 +83,11 @@
 
     void finishPage() {
         assert(mCurrentPage != NULL);
-        mCurrentPage->mPicture->endRecording();
+        assert(mCurrentPage->mPictureRecorder != NULL);
+        assert(mCurrentPage->mPicture == NULL);
+        mCurrentPage->mPicture = mCurrentPage->mPictureRecorder->endRecording();
+        delete mCurrentPage->mPictureRecorder;
+        mCurrentPage->mPictureRecorder = NULL;
         mCurrentPage = NULL;
     }
 
@@ -89,7 +101,7 @@
 
             canvas->clipRect(page->mContentRect);
             canvas->translate(page->mContentRect.left(), page->mContentRect.top());
-            canvas->drawPicture(*page->mPicture);
+            canvas->drawPicture(page->mPicture);
 
             document->endPage();
         }
@@ -97,11 +109,10 @@
     }
 
     void close() {
+        assert(NULL == mCurrentPage);
         for (unsigned i = 0; i < mPages.size(); i++) {
             delete mPages[i];
         }
-        delete mCurrentPage;
-        mCurrentPage = NULL;
     }
 
 private:
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index fcf8f83e..ddcc396 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -51,6 +51,8 @@
     jfieldID    fifoMaxEventCount;
     jfieldID    stringType;
     jfieldID    requiredPermission;
+    jfieldID    maxDelay;
+    jfieldID    isWakeUpSensor;
 } gSensorOffsets;
 
 
@@ -78,6 +80,8 @@
     sensorOffsets.stringType = _env->GetFieldID(sensorClass, "mStringType", "Ljava/lang/String;");
     sensorOffsets.requiredPermission = _env->GetFieldID(sensorClass, "mRequiredPermission",
                                                         "Ljava/lang/String;");
+    sensorOffsets.maxDelay    = _env->GetFieldID(sensorClass, "mMaxDelay",  "I");
+    sensorOffsets.isWakeUpSensor = _env->GetFieldID(sensorClass, "mWakeUpSensor",  "Z");
 }
 
 static jint
@@ -112,6 +116,8 @@
     env->SetObjectField(sensor, sensorOffsets.stringType, stringType);
     env->SetObjectField(sensor, sensorOffsets.requiredPermission,
                         requiredPermission);
+    env->SetIntField(sensor, sensorOffsets.maxDelay, list->getMaxDelay());
+    env->SetBooleanField(sensor, sensorOffsets.isWakeUpSensor, list->isWakeUpSensor());
     next++;
     return size_t(next) < count ? next : 0;
 }
@@ -160,7 +166,8 @@
         ASensorEvent buffer[16];
         while ((n = q->read(buffer, 16)) > 0) {
             for (int i=0 ; i<n ; i++) {
-                if (buffer[i].type == SENSOR_TYPE_STEP_COUNTER) {
+                if (buffer[i].type == SENSOR_TYPE_STEP_COUNTER ||
+                    buffer[i].type == SENSOR_TYPE_WAKE_UP_STEP_COUNTER) {
                     // step-counter returns a uint64, but the java API only deals with floats
                     float value = float(buffer[i].u64.step_counter);
                     env->SetFloatArrayRegion(mScratch, 0, 1, &value);
@@ -175,11 +182,26 @@
                                         gBaseEventQueueClassInfo.dispatchFlushCompleteEvent,
                                         buffer[i].meta_data.sensor);
                 } else {
+                    int8_t status;
+                    switch (buffer[i].type) {
+                    case SENSOR_TYPE_ORIENTATION:
+                    case SENSOR_TYPE_MAGNETIC_FIELD:
+                    case SENSOR_TYPE_ACCELEROMETER:
+                    case SENSOR_TYPE_GYROSCOPE:
+                        status = buffer[i].vector.status;
+                        break;
+                    case SENSOR_TYPE_HEART_RATE:
+                        status = buffer[i].heart_rate.status;
+                        break;
+                    default:
+                        status = SENSOR_STATUS_ACCURACY_HIGH;
+                        break;
+                    }
                     env->CallVoidMethod(mReceiverObject,
                                         gBaseEventQueueClassInfo.dispatchSensorEvent,
                                         buffer[i].sensor,
                                         mScratch,
-                                        buffer[i].vector.status,
+                                        status,
                                         buffer[i].timestamp);
                 }
                 if (env->ExceptionCheck()) {
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
new file mode 100644
index 0000000..69e991d
--- /dev/null
+++ b/core/jni/android_hardware_SoundTrigger.cpp
@@ -0,0 +1,661 @@
+/*
+**
+** Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundTrigger-JNI"
+#include <utils/Log.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+#include <system/sound_trigger.h>
+#include <soundtrigger/SoundTriggerCallback.h>
+#include <soundtrigger/SoundTrigger.h>
+#include <utils/RefBase.h>
+#include <utils/Vector.h>
+#include <binder/IMemory.h>
+#include <binder/MemoryDealer.h>
+
+using namespace android;
+
+static jclass gArrayListClass;
+static struct {
+    jmethodID    add;
+} gArrayListMethods;
+
+static const char* const kSoundTriggerClassPathName = "android/hardware/soundtrigger/SoundTrigger";
+static jclass gSoundTriggerClass;
+
+static const char* const kModuleClassPathName = "android/hardware/soundtrigger/SoundTriggerModule";
+static jclass gModuleClass;
+static struct {
+    jfieldID    mNativeContext;
+    jfieldID    mId;
+} gModuleFields;
+static jmethodID   gPostEventFromNative;
+
+static const char* const kModulePropertiesClassPathName =
+                                     "android/hardware/soundtrigger/SoundTrigger$ModuleProperties";
+static jclass gModulePropertiesClass;
+static jmethodID   gModulePropertiesCstor;
+
+static const char* const kSoundModelClassPathName =
+                                     "android/hardware/soundtrigger/SoundTrigger$SoundModel";
+static jclass gSoundModelClass;
+static struct {
+    jfieldID    data;
+} gSoundModelFields;
+
+static const char* const kKeyPhraseClassPathName =
+                                     "android/hardware/soundtrigger/SoundTrigger$KeyPhrase";
+static jclass gKeyPhraseClass;
+static struct {
+    jfieldID recognitionModes;
+    jfieldID locale;
+    jfieldID text;
+    jfieldID numUsers;
+} gKeyPhraseFields;
+
+static const char* const kKeyPhraseSoundModelClassPathName =
+                                 "android/hardware/soundtrigger/SoundTrigger$KeyPhraseSoundModel";
+static jclass gKeyPhraseSoundModelClass;
+static struct {
+    jfieldID    keyPhrases;
+} gKeyPhraseSoundModelFields;
+
+
+static const char* const kRecognitionEventClassPathName =
+                                     "android/hardware/soundtrigger/SoundTrigger$RecognitionEvent";
+static jclass gRecognitionEventClass;
+static jmethodID   gRecognitionEventCstor;
+
+static const char* const kKeyPhraseRecognitionEventClassPathName =
+                             "android/hardware/soundtrigger/SoundTrigger$KeyPhraseRecognitionEvent";
+static jclass gKeyPhraseRecognitionEventClass;
+static jmethodID   gKeyPhraseRecognitionEventCstor;
+
+static const char* const kKeyPhraseRecognitionExtraClassPathName =
+                             "android/hardware/soundtrigger/SoundTrigger$KeyPhraseRecognitionExtra";
+static jclass gKeyPhraseRecognitionExtraClass;
+static jmethodID   gKeyPhraseRecognitionExtraCstor;
+
+static Mutex gLock;
+
+enum {
+    SOUNDTRIGGER_STATUS_OK = 0,
+    SOUNDTRIGGER_STATUS_ERROR = INT_MIN,
+    SOUNDTRIGGER_PERMISSION_DENIED = -1,
+    SOUNDTRIGGER_STATUS_NO_INIT = -19,
+    SOUNDTRIGGER_STATUS_BAD_VALUE = -22,
+    SOUNDTRIGGER_STATUS_DEAD_OBJECT = -32,
+    SOUNDTRIGGER_INVALID_OPERATION = -38,
+};
+
+enum  {
+    SOUNDTRIGGER_EVENT_RECOGNITION = 1,
+    SOUNDTRIGGER_EVENT_SERVICE_DIED = 2,
+};
+
+// ----------------------------------------------------------------------------
+// ref-counted object for callbacks
+class JNISoundTriggerCallback: public SoundTriggerCallback
+{
+public:
+    JNISoundTriggerCallback(JNIEnv* env, jobject thiz, jobject weak_thiz);
+    ~JNISoundTriggerCallback();
+
+    virtual void onRecognitionEvent(struct sound_trigger_recognition_event *event);
+    virtual void onServiceDied();
+
+private:
+    jclass      mClass;     // Reference to SoundTrigger class
+    jobject     mObject;    // Weak ref to SoundTrigger Java object to call on
+};
+
+JNISoundTriggerCallback::JNISoundTriggerCallback(JNIEnv* env, jobject thiz, jobject weak_thiz)
+{
+
+    // Hold onto the SoundTriggerModule class for use in calling the static method
+    // that posts events to the application thread.
+    jclass clazz = env->GetObjectClass(thiz);
+    if (clazz == NULL) {
+        ALOGE("Can't find class %s", kModuleClassPathName);
+        return;
+    }
+    mClass = (jclass)env->NewGlobalRef(clazz);
+
+    // We use a weak reference so the SoundTriggerModule object can be garbage collected.
+    // The reference is only used as a proxy for callbacks.
+    mObject  = env->NewGlobalRef(weak_thiz);
+}
+
+JNISoundTriggerCallback::~JNISoundTriggerCallback()
+{
+    // remove global references
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    env->DeleteGlobalRef(mObject);
+    env->DeleteGlobalRef(mClass);
+}
+
+void JNISoundTriggerCallback::onRecognitionEvent(struct sound_trigger_recognition_event *event)
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    jobject jEvent;
+
+    jbyteArray jData = NULL;
+    if (event->data_size) {
+        jData = env->NewByteArray(event->data_size);
+        jbyte *nData = env->GetByteArrayElements(jData, NULL);
+        memcpy(nData, (char *)event + event->data_offset, event->data_size);
+        env->ReleaseByteArrayElements(jData, nData, 0);
+    }
+
+    if (event->type == SOUND_MODEL_TYPE_KEYPHRASE) {
+        struct sound_trigger_phrase_recognition_event *phraseEvent =
+                (struct sound_trigger_phrase_recognition_event *)event;
+
+        jobjectArray jExtras = env->NewObjectArray(phraseEvent->num_phrases,
+                                                  gKeyPhraseRecognitionExtraClass, NULL);
+        if (jExtras == NULL) {
+            return;
+        }
+
+        for (size_t i = 0; i < phraseEvent->num_phrases; i++) {
+            jintArray jConfidenceLevels = env->NewIntArray(phraseEvent->phrase_extras[i].num_users);
+            if (jConfidenceLevels == NULL) {
+                return;
+            }
+            jint *nConfidenceLevels = env->GetIntArrayElements(jConfidenceLevels, NULL);
+            memcpy(nConfidenceLevels,
+                   phraseEvent->phrase_extras[i].confidence_levels,
+                   phraseEvent->phrase_extras[i].num_users * sizeof(int));
+            env->ReleaseIntArrayElements(jConfidenceLevels, nConfidenceLevels, 0);
+            jobject jNewExtra = env->NewObject(gKeyPhraseRecognitionExtraClass,
+                                               gKeyPhraseRecognitionExtraCstor,
+                                               jConfidenceLevels,
+                                               phraseEvent->phrase_extras[i].recognition_modes);
+
+            if (jNewExtra == NULL) {
+                return;
+            }
+            env->SetObjectArrayElement(jExtras, i, jNewExtra);
+
+        }
+        jEvent = env->NewObject(gKeyPhraseRecognitionEventClass, gKeyPhraseRecognitionEventCstor,
+                                event->status, event->model, event->capture_available,
+                               event->capture_session, event->capture_delay_ms, jData,
+                               phraseEvent->key_phrase_in_capture, jExtras);
+    } else {
+        jEvent = env->NewObject(gRecognitionEventClass, gRecognitionEventCstor,
+                                event->status, event->model, event->capture_available,
+                                event->capture_session, event->capture_delay_ms, jData);
+    }
+
+
+    env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
+                              SOUNDTRIGGER_EVENT_RECOGNITION, 0, 0, jEvent);
+    if (env->ExceptionCheck()) {
+        ALOGW("An exception occurred while notifying an event.");
+        env->ExceptionClear();
+    }
+}
+
+void JNISoundTriggerCallback::onServiceDied()
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
+                              SOUNDTRIGGER_EVENT_SERVICE_DIED, 0, 0, NULL);
+    if (env->ExceptionCheck()) {
+        ALOGW("An exception occurred while notifying an event.");
+        env->ExceptionClear();
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+static sp<SoundTrigger> getSoundTrigger(JNIEnv* env, jobject thiz)
+{
+    Mutex::Autolock l(gLock);
+    SoundTrigger* const st = (SoundTrigger*)env->GetLongField(thiz,
+                                                         gModuleFields.mNativeContext);
+    return sp<SoundTrigger>(st);
+}
+
+static sp<SoundTrigger> setSoundTrigger(JNIEnv* env, jobject thiz, const sp<SoundTrigger>& module)
+{
+    Mutex::Autolock l(gLock);
+    sp<SoundTrigger> old = (SoundTrigger*)env->GetLongField(thiz,
+                                                         gModuleFields.mNativeContext);
+    if (module.get()) {
+        module->incStrong((void*)setSoundTrigger);
+    }
+    if (old != 0) {
+        old->decStrong((void*)setSoundTrigger);
+    }
+    env->SetLongField(thiz, gModuleFields.mNativeContext, (jlong)module.get());
+    return old;
+}
+
+
+static jint
+android_hardware_SoundTrigger_listModules(JNIEnv *env, jobject clazz,
+                                          jobject jModules)
+{
+    ALOGV("listModules");
+
+    if (jModules == NULL) {
+        ALOGE("listModules NULL AudioPatch ArrayList");
+        return SOUNDTRIGGER_STATUS_BAD_VALUE;
+    }
+    if (!env->IsInstanceOf(jModules, gArrayListClass)) {
+        ALOGE("listModules not an arraylist");
+        return SOUNDTRIGGER_STATUS_BAD_VALUE;
+    }
+
+    unsigned int numModules = 0;
+    struct sound_trigger_module_descriptor *nModules = NULL;
+
+    status_t status = SoundTrigger::listModules(nModules, &numModules);
+    if (status != NO_ERROR || numModules == 0) {
+        return (jint)status;
+    }
+
+    nModules = (struct sound_trigger_module_descriptor *)
+                            calloc(numModules, sizeof(struct sound_trigger_module_descriptor));
+
+    status = SoundTrigger::listModules(nModules, &numModules);
+    ALOGV("listModules SoundTrigger::listModules status %d numModules %d", status, numModules);
+
+    if (status != NO_ERROR) {
+        numModules = 0;
+    }
+
+    for (size_t i = 0; i < numModules; i++) {
+        char str[SOUND_TRIGGER_MAX_STRING_LEN];
+
+        jstring implementor = env->NewStringUTF(nModules[i].properties.implementor);
+        jstring description = env->NewStringUTF(nModules[i].properties.description);
+        SoundTrigger::guidToString(&nModules[i].properties.uuid,
+                                   str,
+                                   SOUND_TRIGGER_MAX_STRING_LEN);
+        jstring uuid = env->NewStringUTF(str);
+
+        ALOGV("listModules module %d id %d description %s maxSoundModels %d",
+              i, nModules[i].handle, nModules[i].properties.description,
+              nModules[i].properties.max_sound_models);
+
+        jobject newModuleDesc = env->NewObject(gModulePropertiesClass, gModulePropertiesCstor,
+                                               nModules[i].handle,
+                                               implementor, description, uuid,
+                                               nModules[i].properties.version,
+                                               nModules[i].properties.max_sound_models,
+                                               nModules[i].properties.max_key_phrases,
+                                               nModules[i].properties.max_users,
+                                               nModules[i].properties.recognition_modes,
+                                               nModules[i].properties.capture_transition,
+                                               nModules[i].properties.max_buffer_ms,
+                                               nModules[i].properties.concurrent_capture,
+                                               nModules[i].properties.power_consumption_mw);
+
+        env->DeleteLocalRef(implementor);
+        env->DeleteLocalRef(description);
+        env->DeleteLocalRef(uuid);
+        if (newModuleDesc == NULL) {
+            status = SOUNDTRIGGER_STATUS_ERROR;
+            goto exit;
+        }
+        env->CallBooleanMethod(jModules, gArrayListMethods.add, newModuleDesc);
+    }
+
+exit:
+    free(nModules);
+    return (jint) status;
+}
+
+static void
+android_hardware_SoundTrigger_setup(JNIEnv *env, jobject thiz, jobject weak_this)
+{
+    ALOGV("setup");
+
+    sp<JNISoundTriggerCallback> callback = new JNISoundTriggerCallback(env, thiz, weak_this);
+
+    sound_trigger_module_handle_t handle =
+            (sound_trigger_module_handle_t)env->GetIntField(thiz, gModuleFields.mId);
+
+    sp<SoundTrigger> module = SoundTrigger::attach(handle, callback);
+    if (module == 0) {
+        return;
+    }
+
+    setSoundTrigger(env, thiz, module);
+}
+
+static void
+android_hardware_SoundTrigger_detach(JNIEnv *env, jobject thiz)
+{
+    ALOGV("detach");
+    sp<SoundTrigger> module = setSoundTrigger(env, thiz, 0);
+    ALOGV("detach module %p", module.get());
+    if (module != 0) {
+        ALOGV("detach module->detach()");
+        module->detach();
+    }
+}
+
+static void
+android_hardware_SoundTrigger_finalize(JNIEnv *env, jobject thiz)
+{
+    ALOGV("finalize");
+    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
+    if (module != 0) {
+        ALOGW("SoundTrigger finalized without being detached");
+    }
+    android_hardware_SoundTrigger_detach(env, thiz);
+}
+
+static jint
+android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz,
+                                             jobject jSoundModel, jintArray jHandle)
+{
+    jint status = SOUNDTRIGGER_STATUS_OK;
+    char *nData = NULL;
+    struct sound_trigger_sound_model *nSoundModel;
+    jbyteArray jData;
+    sp<MemoryDealer> memoryDealer;
+    sp<IMemory> memory;
+    size_t size;
+    sound_model_handle_t handle;
+
+    ALOGV("loadSoundModel");
+    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
+    if (module == NULL) {
+        return SOUNDTRIGGER_STATUS_ERROR;
+    }
+    if (jHandle == NULL) {
+        return SOUNDTRIGGER_STATUS_BAD_VALUE;
+    }
+    jsize jHandleLen = env->GetArrayLength(jHandle);
+    if (jHandleLen == 0) {
+        return SOUNDTRIGGER_STATUS_BAD_VALUE;
+    }
+    jint *nHandle = env->GetIntArrayElements(jHandle, NULL);
+    if (nHandle == NULL) {
+        return SOUNDTRIGGER_STATUS_ERROR;
+    }
+    if (!env->IsInstanceOf(jSoundModel, gSoundModelClass)) {
+        status = SOUNDTRIGGER_STATUS_BAD_VALUE;
+        goto exit;
+    }
+    size_t offset;
+    sound_trigger_sound_model_type_t type;
+    if (env->IsInstanceOf(jSoundModel, gKeyPhraseSoundModelClass)) {
+        offset = sizeof(struct sound_trigger_phrase_sound_model);
+        type = SOUND_MODEL_TYPE_KEYPHRASE;
+    } else {
+        offset = sizeof(struct sound_trigger_sound_model);
+        type = SOUND_MODEL_TYPE_UNKNOWN;
+    }
+    jData = (jbyteArray)env->GetObjectField(jSoundModel, gSoundModelFields.data);
+    if (jData == NULL) {
+        status = SOUNDTRIGGER_STATUS_BAD_VALUE;
+        goto exit;
+    }
+    size = env->GetArrayLength(jData);
+
+    nData = (char *)env->GetByteArrayElements(jData, NULL);
+    if (jData == NULL) {
+        status = SOUNDTRIGGER_STATUS_ERROR;
+        goto exit;
+    }
+
+    memoryDealer = new MemoryDealer(offset + size, "SoundTrigge-JNI::LoadModel");
+    if (memoryDealer == 0) {
+        status = SOUNDTRIGGER_STATUS_ERROR;
+        goto exit;
+    }
+    memory = memoryDealer->allocate(offset + size);
+    if (memory == 0 || memory->pointer() == NULL) {
+        status = SOUNDTRIGGER_STATUS_ERROR;
+        goto exit;
+    }
+
+    nSoundModel = (struct sound_trigger_sound_model *)memory->pointer();
+
+    nSoundModel->type = type;
+    nSoundModel->data_size = size;
+    nSoundModel->data_offset = offset;
+    memcpy((char *)nSoundModel + offset, nData, size);
+    if (type == SOUND_MODEL_TYPE_KEYPHRASE) {
+        struct sound_trigger_phrase_sound_model *phraseModel =
+                (struct sound_trigger_phrase_sound_model *)nSoundModel;
+
+        jobjectArray jPhrases =
+            (jobjectArray)env->GetObjectField(jSoundModel, gKeyPhraseSoundModelFields.keyPhrases);
+        if (jPhrases == NULL) {
+            status = SOUNDTRIGGER_STATUS_BAD_VALUE;
+            goto exit;
+        }
+
+        size_t numPhrases = env->GetArrayLength(jPhrases);
+        phraseModel->num_phrases = numPhrases;
+        ALOGV("loadSoundModel numPhrases %d", numPhrases);
+        for (size_t i = 0; i < numPhrases; i++) {
+            jobject jPhrase = env->GetObjectArrayElement(jPhrases, i);
+            phraseModel->phrases[i].recognition_mode =
+                                    env->GetIntField(jPhrase,gKeyPhraseFields.recognitionModes);
+            phraseModel->phrases[i].num_users =
+                                    env->GetIntField(jPhrase, gKeyPhraseFields.numUsers);
+            jstring jLocale = (jstring)env->GetObjectField(jPhrase, gKeyPhraseFields.locale);
+            const char *nLocale = env->GetStringUTFChars(jLocale, NULL);
+            strncpy(phraseModel->phrases[i].locale,
+                    nLocale,
+                    SOUND_TRIGGER_MAX_LOCALE_LEN);
+            jstring jText = (jstring)env->GetObjectField(jPhrase, gKeyPhraseFields.text);
+            const char *nText = env->GetStringUTFChars(jText, NULL);
+            strncpy(phraseModel->phrases[i].text,
+                    nText,
+                    SOUND_TRIGGER_MAX_STRING_LEN);
+
+            env->ReleaseStringUTFChars(jLocale, nLocale);
+            env->DeleteLocalRef(jLocale);
+            env->ReleaseStringUTFChars(jText, nText);
+            env->DeleteLocalRef(jText);
+            ALOGV("loadSoundModel phrases %d text %s locale %s",
+                  i, phraseModel->phrases[i].text, phraseModel->phrases[i].locale);
+        }
+        env->DeleteLocalRef(jPhrases);
+    }
+    status = module->loadSoundModel(memory, &handle);
+    ALOGV("loadSoundModel status %d handle %d", status, handle);
+
+exit:
+    if (nHandle != NULL) {
+        nHandle[0] = (jint)handle;
+        env->ReleaseIntArrayElements(jHandle, nHandle, NULL);
+    }
+    if (nData != NULL) {
+        env->ReleaseByteArrayElements(jData, (jbyte *)nData, NULL);
+    }
+    return status;
+}
+
+static jint
+android_hardware_SoundTrigger_unloadSoundModel(JNIEnv *env, jobject thiz,
+                                               jint jHandle)
+{
+    jint status = SOUNDTRIGGER_STATUS_OK;
+    ALOGV("unloadSoundModel");
+    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
+    if (module == NULL) {
+        return SOUNDTRIGGER_STATUS_ERROR;
+    }
+    status = module->unloadSoundModel((sound_model_handle_t)jHandle);
+
+    return status;
+}
+
+static jint
+android_hardware_SoundTrigger_startRecognition(JNIEnv *env, jobject thiz,
+                                               jint jHandle, jbyteArray jData)
+{
+    jint status = SOUNDTRIGGER_STATUS_OK;
+    ALOGV("startRecognition");
+    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
+    if (module == NULL) {
+        return SOUNDTRIGGER_STATUS_ERROR;
+    }
+    jsize dataSize = 0;
+    char *nData = NULL;
+    sp<IMemory> memory;
+    if (jData != NULL) {
+        dataSize = env->GetArrayLength(jData);
+        if (dataSize == 0) {
+            return SOUNDTRIGGER_STATUS_BAD_VALUE;
+        }
+        nData = (char *)env->GetByteArrayElements(jData, NULL);
+        if (nData == NULL) {
+            return SOUNDTRIGGER_STATUS_ERROR;
+        }
+        sp<MemoryDealer> memoryDealer =
+                new MemoryDealer(dataSize, "SoundTrigge-JNI::StartRecognition");
+        if (memoryDealer == 0) {
+            return SOUNDTRIGGER_STATUS_ERROR;
+        }
+        memory = memoryDealer->allocate(dataSize);
+        if (memory == 0 || memory->pointer() == NULL) {
+            return SOUNDTRIGGER_STATUS_ERROR;
+        }
+        memcpy(memory->pointer(), nData, dataSize);
+    }
+
+    status = module->startRecognition(jHandle, memory);
+    return status;
+}
+
+static jint
+android_hardware_SoundTrigger_stopRecognition(JNIEnv *env, jobject thiz,
+                                               jint jHandle)
+{
+    jint status = SOUNDTRIGGER_STATUS_OK;
+    ALOGV("stopRecognition");
+    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
+    if (module == NULL) {
+        return SOUNDTRIGGER_STATUS_ERROR;
+    }
+    status = module->stopRecognition(jHandle);
+    return status;
+}
+
+static JNINativeMethod gMethods[] = {
+    {"listModules",
+        "(Ljava/util/ArrayList;)I",
+        (void *)android_hardware_SoundTrigger_listModules},
+};
+
+
+static JNINativeMethod gModuleMethods[] = {
+    {"native_setup",
+        "(Ljava/lang/Object;)V",
+        (void *)android_hardware_SoundTrigger_setup},
+    {"native_finalize",
+        "()V",
+        (void *)android_hardware_SoundTrigger_finalize},
+    {"detach",
+        "()V",
+        (void *)android_hardware_SoundTrigger_detach},
+    {"loadSoundModel",
+        "(Landroid/hardware/soundtrigger/SoundTrigger$SoundModel;[I)I",
+        (void *)android_hardware_SoundTrigger_loadSoundModel},
+    {"unloadSoundModel",
+        "(I)I",
+        (void *)android_hardware_SoundTrigger_unloadSoundModel},
+    {"startRecognition",
+        "(I[B)I",
+        (void *)android_hardware_SoundTrigger_startRecognition},
+    {"stopRecognition",
+        "(I)I",
+        (void *)android_hardware_SoundTrigger_stopRecognition},
+};
+
+int register_android_hardware_SoundTrigger(JNIEnv *env)
+{
+    jclass arrayListClass = env->FindClass("java/util/ArrayList");
+    gArrayListClass = (jclass) env->NewGlobalRef(arrayListClass);
+    gArrayListMethods.add = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");
+
+    jclass lClass = env->FindClass(kSoundTriggerClassPathName);
+    gSoundTriggerClass = (jclass) env->NewGlobalRef(lClass);
+
+    jclass moduleClass = env->FindClass(kModuleClassPathName);
+    gModuleClass = (jclass) env->NewGlobalRef(moduleClass);
+    gPostEventFromNative = env->GetStaticMethodID(moduleClass, "postEventFromNative",
+                                            "(Ljava/lang/Object;IIILjava/lang/Object;)V");
+    gModuleFields.mNativeContext = env->GetFieldID(moduleClass, "mNativeContext", "J");
+    gModuleFields.mId = env->GetFieldID(moduleClass, "mId", "I");
+
+
+    jclass modulePropertiesClass = env->FindClass(kModulePropertiesClassPathName);
+    gModulePropertiesClass = (jclass) env->NewGlobalRef(modulePropertiesClass);
+    gModulePropertiesCstor = env->GetMethodID(modulePropertiesClass, "<init>",
+                              "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIIZIZI)V");
+
+    jclass soundModelClass = env->FindClass(kSoundModelClassPathName);
+    gSoundModelClass = (jclass) env->NewGlobalRef(soundModelClass);
+    gSoundModelFields.data = env->GetFieldID(soundModelClass, "data", "[B");
+
+    jclass keyPhraseClass = env->FindClass(kKeyPhraseClassPathName);
+    gKeyPhraseClass = (jclass) env->NewGlobalRef(keyPhraseClass);
+    gKeyPhraseFields.recognitionModes = env->GetFieldID(keyPhraseClass, "recognitionModes", "I");
+    gKeyPhraseFields.locale = env->GetFieldID(keyPhraseClass, "locale", "Ljava/lang/String;");
+    gKeyPhraseFields.text = env->GetFieldID(keyPhraseClass, "text", "Ljava/lang/String;");
+    gKeyPhraseFields.numUsers = env->GetFieldID(keyPhraseClass, "numUsers", "I");
+
+    jclass keyPhraseSoundModelClass = env->FindClass(kKeyPhraseSoundModelClassPathName);
+    gKeyPhraseSoundModelClass = (jclass) env->NewGlobalRef(keyPhraseSoundModelClass);
+    gKeyPhraseSoundModelFields.keyPhrases = env->GetFieldID(keyPhraseSoundModelClass,
+                                         "keyPhrases",
+                                         "[Landroid/hardware/soundtrigger/SoundTrigger$KeyPhrase;");
+
+
+    jclass recognitionEventClass = env->FindClass(kRecognitionEventClassPathName);
+    gRecognitionEventClass = (jclass) env->NewGlobalRef(recognitionEventClass);
+    gRecognitionEventCstor = env->GetMethodID(recognitionEventClass, "<init>",
+                                              "(IIZII[B)V");
+
+    jclass keyPhraseRecognitionEventClass = env->FindClass(kKeyPhraseRecognitionEventClassPathName);
+    gKeyPhraseRecognitionEventClass = (jclass) env->NewGlobalRef(keyPhraseRecognitionEventClass);
+    gKeyPhraseRecognitionEventCstor = env->GetMethodID(keyPhraseRecognitionEventClass, "<init>",
+              "(IIZII[BZ[Landroid/hardware/soundtrigger/SoundTrigger$KeyPhraseRecognitionExtra;)V");
+
+
+    jclass keyPhraseRecognitionExtraClass = env->FindClass(kKeyPhraseRecognitionExtraClassPathName);
+    gKeyPhraseRecognitionExtraClass = (jclass) env->NewGlobalRef(keyPhraseRecognitionExtraClass);
+    gKeyPhraseRecognitionExtraCstor = env->GetMethodID(keyPhraseRecognitionExtraClass, "<init>",
+                                              "([II)V");
+
+    int status = AndroidRuntime::registerNativeMethods(env,
+                kSoundTriggerClassPathName, gMethods, NELEM(gMethods));
+
+    if (status == 0) {
+        status = AndroidRuntime::registerNativeMethods(env,
+                kModuleClassPathName, gModuleMethods, NELEM(gModuleMethods));
+    }
+
+    return status;
+}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index bf47dd3..ee4c619 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -244,6 +244,12 @@
 }
 
 static jint
+android_media_AudioSystem_newAudioSessionId(JNIEnv *env, jobject thiz)
+{
+    return AudioSystem::newAudioSessionId();
+}
+
+static jint
 android_media_AudioSystem_setParameters(JNIEnv *env, jobject thiz, jstring keyValuePairs)
 {
     const jchar* c_keyValuePairs = env->GetStringCritical(keyValuePairs, 0);
@@ -1295,6 +1301,7 @@
     {"isStreamActive",      "(II)Z",    (void *)android_media_AudioSystem_isStreamActive},
     {"isStreamActiveRemotely","(II)Z",  (void *)android_media_AudioSystem_isStreamActiveRemotely},
     {"isSourceActive",      "(I)Z",     (void *)android_media_AudioSystem_isSourceActive},
+    {"newAudioSessionId",   "()I",      (void *)android_media_AudioSystem_newAudioSessionId},
     {"setDeviceConnectionState", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setDeviceConnectionState},
     {"getDeviceConnectionState", "(ILjava/lang/String;)I",  (void *)android_media_AudioSystem_getDeviceConnectionState},
     {"setPhoneState",       "(I)I",     (void *)android_media_AudioSystem_setPhoneState},
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index d032cb6..9fa5ec9 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -91,53 +91,9 @@
 } gRectClassInfo;
 
 // ----------------------------------------------------------------------------
-// Caching
-// ----------------------------------------------------------------------------
-
-static void android_view_GLES20Canvas_flushCaches(JNIEnv* env, jobject clazz,
-        jint mode) {
-    if (Caches::hasInstance()) {
-        Caches::getInstance().flush(static_cast<Caches::FlushMode>(mode));
-    }
-}
-
-static jboolean android_view_GLES20Canvas_initCaches(JNIEnv* env, jobject clazz) {
-    if (Caches::hasInstance()) {
-        return Caches::getInstance().init() ? JNI_TRUE : JNI_FALSE;
-    }
-    return JNI_FALSE;
-}
-
-static void android_view_GLES20Canvas_terminateCaches(JNIEnv* env, jobject clazz) {
-    if (Caches::hasInstance()) {
-        Caches::getInstance().terminate();
-    }
-}
-
-// ----------------------------------------------------------------------------
-// Caching
-// ----------------------------------------------------------------------------
-
-static void android_view_GLES20Canvas_initAtlas(JNIEnv* env, jobject clazz,
-        jobject graphicBuffer, jlongArray atlasMapArray, jint count) {
-
-    sp<GraphicBuffer> buffer = graphicBufferForJavaObject(env, graphicBuffer);
-    jlong* jAtlasMap = env->GetLongArrayElements(atlasMapArray, NULL);
-    Caches::getInstance().assetAtlas.init(buffer, jAtlasMap, count);
-    env->ReleaseLongArrayElements(atlasMapArray, jAtlasMap, 0);
-}
-
-// ----------------------------------------------------------------------------
 // Constructors
 // ----------------------------------------------------------------------------
 
-static jlong android_view_GLES20Canvas_createRenderer(JNIEnv* env, jobject clazz) {
-    RENDERER_LOGD("Create OpenGLRenderer");
-    OpenGLRenderer* renderer = new OpenGLRenderer();
-    renderer->initProperties();
-    return reinterpret_cast<jlong>(renderer);
-}
-
 static void android_view_GLES20Canvas_destroyRenderer(JNIEnv* env, jobject clazz,
         jlong rendererPtr) {
     OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
@@ -174,10 +130,6 @@
     renderer->finish();
 }
 
-static jint android_view_GLES20Canvas_getStencilSize(JNIEnv* env, jobject clazz) {
-    return Stencil::getStencilSize();
-}
-
 static void android_view_GLES20Canvas_setProperty(JNIEnv* env,
         jobject clazz, jstring name, jstring value) {
     if (!Caches::hasInstance()) {
@@ -373,7 +325,7 @@
         jlong rendererPtr, jlong matrixPtr) {
     OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
     SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr);
-    renderer->setMatrix(matrix);
+    renderer->setMatrix(matrix ? *matrix : SkMatrix::I());
 }
 
 static void android_view_GLES20Canvas_getMatrix(JNIEnv* env, jobject clazz,
@@ -387,7 +339,7 @@
         jlong rendererPtr, jlong matrixPtr) {
     OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
     SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr);
-    renderer->concatMatrix(matrix);
+    renderer->concatMatrix(*matrix);
 }
 
 // ----------------------------------------------------------------------------
@@ -430,7 +382,7 @@
     OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
     SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
-    renderer->drawBitmap(bitmap, matrix, paint);
+    renderer->drawBitmap(bitmap, *matrix, paint);
 }
 
 static void android_view_GLES20Canvas_drawBitmapData(JNIEnv* env, jobject clazz,
@@ -577,15 +529,6 @@
     }
 }
 
-static void android_view_GLES20Canvas_drawRects(JNIEnv* env, jobject clazz,
-        jlong rendererPtr, jfloatArray rects, jint count, jlong paintPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
-    jfloat* storage = env->GetFloatArrayElements(rects, NULL);
-    SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr);
-    renderer->drawRects(storage, count, paint);
-    env->ReleaseFloatArrayElements(rects, storage, 0);
-}
-
 static void android_view_GLES20Canvas_drawPoints(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jfloatArray points, jint offset, jint count, jlong paintPtr) {
     OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
@@ -932,39 +875,6 @@
     renderer->drawLayer(layer, x, y);
 }
 
-static jboolean android_view_GLES20Canvas_copyLayer(JNIEnv* env, jobject clazz,
-        jlong layerPtr, jlong bitmapPtr) {
-    Layer* layer = reinterpret_cast<Layer*>(layerPtr);
-    SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapPtr);
-    return LayerRenderer::copyLayer(layer, bitmap);
-}
-
-static void android_view_GLES20Canvas_pushLayerUpdate(JNIEnv* env, jobject clazz,
-        jlong rendererPtr, jlong layerPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
-    Layer* layer = reinterpret_cast<Layer*>(layerPtr);
-    renderer->pushLayerUpdate(layer);
-}
-
-static void android_view_GLES20Canvas_cancelLayerUpdate(JNIEnv* env, jobject clazz,
-        jlong rendererPtr, jlong layerPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
-    Layer* layer = reinterpret_cast<Layer*>(layerPtr);
-    renderer->cancelLayerUpdate(layer);
-}
-
-static void android_view_GLES20Canvas_clearLayerUpdates(JNIEnv* env, jobject clazz,
-        jlong rendererPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
-    renderer->clearLayerUpdates();
-}
-
-static void android_view_GLES20Canvas_flushLayerUpdates(JNIEnv* env, jobject clazz,
-        jlong rendererPtr) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
-    renderer->flushLayerUpdates();
-}
-
 #endif // USE_OPENGL_RENDERER
 
 // ----------------------------------------------------------------------------
@@ -1009,14 +919,7 @@
     { "nIsAvailable",       "()Z",             (void*) android_view_GLES20Canvas_isAvailable },
 
 #ifdef USE_OPENGL_RENDERER
-    { "nFlushCaches",       "(I)V",            (void*) android_view_GLES20Canvas_flushCaches },
-    { "nInitCaches",        "()Z",             (void*) android_view_GLES20Canvas_initCaches },
-    { "nTerminateCaches",   "()V",             (void*) android_view_GLES20Canvas_terminateCaches },
 
-    { "nInitAtlas",         "(Landroid/view/GraphicBuffer;[JI)V",
-            (void*) android_view_GLES20Canvas_initAtlas },
-
-    { "nCreateRenderer",    "()J",             (void*) android_view_GLES20Canvas_createRenderer },
     { "nDestroyRenderer",   "(J)V",            (void*) android_view_GLES20Canvas_destroyRenderer },
     { "nSetViewport",       "(JII)V",          (void*) android_view_GLES20Canvas_setViewport },
     { "nPrepare",           "(JZ)I",           (void*) android_view_GLES20Canvas_prepare },
@@ -1025,9 +928,6 @@
     { "nSetProperty",           "(Ljava/lang/String;Ljava/lang/String;)V",
             (void*) android_view_GLES20Canvas_setProperty },
 
-
-    { "nGetStencilSize",    "()I",             (void*) android_view_GLES20Canvas_getStencilSize },
-
     { "nCallDrawGLFunction", "(JJ)I",          (void*) android_view_GLES20Canvas_callDrawGLFunction },
 
     { "nSave",              "(JI)I",           (void*) android_view_GLES20Canvas_save },
@@ -1067,7 +967,6 @@
     { "nDrawColor",         "(JII)V",          (void*) android_view_GLES20Canvas_drawColor },
     { "nDrawRect",          "(JFFFFJ)V",       (void*) android_view_GLES20Canvas_drawRect },
     { "nDrawRects",         "(JJJ)V",          (void*) android_view_GLES20Canvas_drawRegionAsRects },
-    { "nDrawRects",         "(J[FIJ)V",        (void*) android_view_GLES20Canvas_drawRects },
     { "nDrawRoundRect",     "(JFFFFFFJ)V",     (void*) android_view_GLES20Canvas_drawRoundRect },
     { "nDrawCircle",        "(JFFFJ)V",        (void*) android_view_GLES20Canvas_drawCircle },
     { "nDrawCircle",        "(JJJJJ)V",        (void*) android_view_GLES20Canvas_drawCircleProps },
@@ -1107,11 +1006,6 @@
     { "nCreateDisplayListRenderer", "()J",     (void*) android_view_GLES20Canvas_createDisplayListRenderer },
 
     { "nDrawLayer",              "(JJFF)V",    (void*) android_view_GLES20Canvas_drawLayer },
-    { "nCopyLayer",              "(JJ)Z",      (void*) android_view_GLES20Canvas_copyLayer },
-    { "nClearLayerUpdates",      "(J)V",       (void*) android_view_GLES20Canvas_clearLayerUpdates },
-    { "nFlushLayerUpdates",      "(J)V",       (void*) android_view_GLES20Canvas_flushLayerUpdates },
-    { "nPushLayerUpdate",        "(JJ)V",      (void*) android_view_GLES20Canvas_pushLayerUpdate },
-    { "nCancelLayerUpdate",      "(JJ)V",      (void*) android_view_GLES20Canvas_cancelLayerUpdate },
 
     { "nGetMaximumTextureWidth",  "()I",       (void*) android_view_GLES20Canvas_getMaxTextureWidth },
     { "nGetMaximumTextureHeight", "()I",       (void*) android_view_GLES20Canvas_getMaxTextureHeight },
diff --git a/core/jni/android_view_GLRenderer.cpp b/core/jni/android_view_GLRenderer.cpp
deleted file mode 100644
index d0269a3..0000000
--- a/core/jni/android_view_GLRenderer.cpp
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2010 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.
- */
-
-#define LOG_TAG "GLRenderer"
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include <android_runtime/AndroidRuntime.h>
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <EGL/egl_cache.h>
-
-#include <utils/Timers.h>
-
-#include <private/hwui/DrawGlInfo.h>
-
-#include <Caches.h>
-#include <Extensions.h>
-#include <LayerRenderer.h>
-#include <RenderNode.h>
-
-#ifdef USE_OPENGL_RENDERER
-    EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface);
-#endif
-
-namespace android {
-
-/**
- * Note: OpenGLRenderer JNI layer is generated and compiled only on supported
- *       devices. This means all the logic must be compiled only when the
- *       preprocessor variable USE_OPENGL_RENDERER is defined.
- */
-#ifdef USE_OPENGL_RENDERER
-
-// ----------------------------------------------------------------------------
-// Defines
-// ----------------------------------------------------------------------------
-
-// Debug
-#define DEBUG_RENDERER 0
-
-// Debug
-#if DEBUG_RENDERER
-    #define RENDERER_LOGD(...) ALOGD(__VA_ARGS__)
-#else
-    #define RENDERER_LOGD(...)
-#endif
-
-// ----------------------------------------------------------------------------
-// Surface and display management
-// ----------------------------------------------------------------------------
-
-static jboolean android_view_GLRenderer_preserveBackBuffer(JNIEnv* env, jobject clazz) {
-    EGLDisplay display = eglGetCurrentDisplay();
-    EGLSurface surface = eglGetCurrentSurface(EGL_DRAW);
-
-    eglGetError();
-    eglSurfaceAttrib(display, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED);
-
-    EGLint error = eglGetError();
-    if (error != EGL_SUCCESS) {
-        RENDERER_LOGD("Could not enable buffer preserved swap behavior (%x)", error);
-    }
-
-    return error == EGL_SUCCESS;
-}
-
-static jboolean android_view_GLRenderer_isBackBufferPreserved(JNIEnv* env, jobject clazz) {
-    EGLDisplay display = eglGetCurrentDisplay();
-    EGLSurface surface = eglGetCurrentSurface(EGL_DRAW);
-    EGLint value;
-
-    eglGetError();
-    eglQuerySurface(display, surface, EGL_SWAP_BEHAVIOR, &value);
-
-    EGLint error = eglGetError();
-    if (error != EGL_SUCCESS) {
-        RENDERER_LOGD("Could not query buffer preserved swap behavior (%x)", error);
-    }
-
-    return error == EGL_SUCCESS && value == EGL_BUFFER_PRESERVED;
-}
-
-// ----------------------------------------------------------------------------
-// Tracing and debugging
-// ----------------------------------------------------------------------------
-
-static bool android_view_GLRenderer_loadProperties(JNIEnv* env, jobject clazz) {
-    if (uirenderer::Caches::hasInstance()) {
-        return uirenderer::Caches::getInstance().initProperties();
-    }
-    return false;
-}
-
-static void android_view_GLRenderer_beginFrame(JNIEnv* env, jobject clazz,
-        jintArray size) {
-
-    EGLDisplay display = eglGetCurrentDisplay();
-    EGLSurface surface = eglGetCurrentSurface(EGL_DRAW);
-
-    if (size) {
-        EGLint value;
-        jint* storage = env->GetIntArrayElements(size, NULL);
-
-        eglQuerySurface(display, surface, EGL_WIDTH, &value);
-        storage[0] = value;
-
-        eglQuerySurface(display, surface, EGL_HEIGHT, &value);
-        storage[1] = value;
-
-        env->ReleaseIntArrayElements(size, storage, 0);
-    }
-
-    eglBeginFrame(display, surface);
-}
-
-static jlong android_view_GLRenderer_getSystemTime(JNIEnv* env, jobject clazz) {
-    if (uirenderer::Extensions::getInstance().hasNvSystemTime()) {
-        return eglGetSystemTimeNV();
-    }
-    return systemTime(SYSTEM_TIME_MONOTONIC);
-}
-
-static void android_view_GLRenderer_destroyLayer(JNIEnv* env, jobject clazz,
-        jlong layerPtr) {
-    using namespace android::uirenderer;
-    Layer* layer = reinterpret_cast<Layer*>(layerPtr);
-    LayerRenderer::destroyLayer(layer);
-}
-
-static void android_view_GLRenderer_prepareTree(JNIEnv* env, jobject clazz,
-        jlong renderNodePtr) {
-    using namespace android::uirenderer;
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    TreeInfo ignoredInfo;
-    renderNode->prepareTree(ignoredInfo);
-}
-
-static void android_view_GLRenderer_invokeFunctor(JNIEnv* env, jobject clazz,
-        jlong functorPtr, jboolean hasContext) {
-    using namespace android::uirenderer;
-    Functor* functor = reinterpret_cast<Functor*>(functorPtr);
-    DrawGlInfo::Mode mode = hasContext ? DrawGlInfo::kModeProcess : DrawGlInfo::kModeProcessNoContext;
-    (*functor)(mode, NULL);
-}
-
-#endif // USE_OPENGL_RENDERER
-
-// ----------------------------------------------------------------------------
-// Shaders
-// ----------------------------------------------------------------------------
-
-static void android_view_GLRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz,
-        jstring diskCachePath) {
-
-    const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL);
-    egl_cache_t::get()->setCacheFilename(cacheArray);
-    env->ReleaseStringUTFChars(diskCachePath, cacheArray);
-}
-
-// ----------------------------------------------------------------------------
-// JNI Glue
-// ----------------------------------------------------------------------------
-
-const char* const kClassPathName = "android/view/GLRenderer";
-
-static JNINativeMethod gMethods[] = {
-#ifdef USE_OPENGL_RENDERER
-    { "isBackBufferPreserved", "()Z",   (void*) android_view_GLRenderer_isBackBufferPreserved },
-    { "preserveBackBuffer",    "()Z",   (void*) android_view_GLRenderer_preserveBackBuffer },
-    { "loadProperties",        "()Z",   (void*) android_view_GLRenderer_loadProperties },
-
-    { "beginFrame",            "([I)V", (void*) android_view_GLRenderer_beginFrame },
-
-    { "getSystemTime",         "()J",   (void*) android_view_GLRenderer_getSystemTime },
-    { "nDestroyLayer",         "(J)V",  (void*) android_view_GLRenderer_destroyLayer },
-    { "nPrepareTree", "(J)V", (void*) android_view_GLRenderer_prepareTree },
-    { "nInvokeFunctor",        "(JZ)V", (void*) android_view_GLRenderer_invokeFunctor },
-#endif
-
-    { "setupShadersDiskCache", "(Ljava/lang/String;)V",
-            (void*) android_view_GLRenderer_setupShadersDiskCache },
-};
-
-int register_android_view_GLRenderer(JNIEnv* env) {
-    return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
-}
-
-};
diff --git a/core/jni/android_view_GraphicBuffer.cpp b/core/jni/android_view_GraphicBuffer.cpp
index 8a426ac..0210bd9 100644
--- a/core/jni/android_view_GraphicBuffer.cpp
+++ b/core/jni/android_view_GraphicBuffer.cpp
@@ -21,6 +21,7 @@
 
 #include "android_os_Parcel.h"
 #include "android_view_GraphicBuffer.h"
+#include "android/graphics/GraphicsJNI.h"
 
 #include <android_runtime/AndroidRuntime.h>
 
@@ -75,7 +76,7 @@
 
 static struct {
     jfieldID mSurfaceFormat;
-    jmethodID safeCanvasSwap;
+    jmethodID setNativeBitmap;
 } gCanvasClassInfo;
 
 #define GET_INT(object, field) \
@@ -197,12 +198,11 @@
     }
 
     SET_INT(canvas, gCanvasClassInfo.mSurfaceFormat, buffer->getPixelFormat());
-
-    SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap));
-    INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false);
+    INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, reinterpret_cast<jlong>(&bitmap));
 
     SkRect clipRect;
     clipRect.set(rect.left, rect.top, rect.right, rect.bottom);
+    SkCanvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas);
     nativeCanvas->clipRect(clipRect);
 
     if (dirtyRect) {
@@ -218,8 +218,7 @@
 
     GraphicBufferWrapper* wrapper =
                 reinterpret_cast<GraphicBufferWrapper*>(wrapperHandle);
-    SkCanvas* nativeCanvas = SkNEW(SkCanvas);
-    INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false);
+    INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, (jlong)0);
 
     if (wrapper) {
         status_t status = wrapper->buffer->unlock();
@@ -319,7 +318,7 @@
 
     FIND_CLASS(clazz, "android/graphics/Canvas");
     GET_FIELD_ID(gCanvasClassInfo.mSurfaceFormat, clazz, "mSurfaceFormat", "I");
-    GET_METHOD_ID(gCanvasClassInfo.safeCanvasSwap, clazz, "safeCanvasSwap", "(JZ)V");
+    GET_METHOD_ID(gCanvasClassInfo.setNativeBitmap, clazz, "setNativeBitmap", "(J)V");
 
     return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
 }
diff --git a/core/jni/android_view_HardwareLayer.cpp b/core/jni/android_view_HardwareLayer.cpp
index 33a2705..64b077b 100644
--- a/core/jni/android_view_HardwareLayer.cpp
+++ b/core/jni/android_view_HardwareLayer.cpp
@@ -43,21 +43,6 @@
 
 #ifdef USE_OPENGL_RENDERER
 
-static jlong android_view_HardwareLayer_createTextureLayer(JNIEnv* env, jobject clazz) {
-    Layer* layer = LayerRenderer::createTextureLayer();
-    if (!layer) return 0;
-
-    return reinterpret_cast<jlong>( new DeferredLayerUpdater(layer) );
-}
-
-static jlong android_view_HardwareLayer_createRenderLayer(JNIEnv* env, jobject clazz,
-        jint width, jint height) {
-    Layer* layer = LayerRenderer::createRenderLayer(width, height);
-    if (!layer) return 0;
-
-    return reinterpret_cast<jlong>( new DeferredLayerUpdater(layer) );
-}
-
 static void android_view_HardwareLayer_onTextureDestroyed(JNIEnv* env, jobject clazz,
         jlong layerUpdaterPtr) {
     DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr);
@@ -110,13 +95,6 @@
     layer->setDisplayList(displayList, left, top, right, bottom);
 }
 
-static jboolean android_view_HardwareLayer_flushChanges(JNIEnv* env, jobject clazz,
-        jlong layerUpdaterPtr) {
-    DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr);
-    TreeInfo ignoredInfo;
-    return layer->apply(ignoredInfo);
-}
-
 static jlong android_view_HardwareLayer_getLayer(JNIEnv* env, jobject clazz,
         jlong layerUpdaterPtr) {
     DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr);
@@ -140,8 +118,6 @@
 static JNINativeMethod gMethods[] = {
 #ifdef USE_OPENGL_RENDERER
 
-    { "nCreateTextureLayer",     "()J",        (void*) android_view_HardwareLayer_createTextureLayer },
-    { "nCreateRenderLayer",      "(II)J",      (void*) android_view_HardwareLayer_createRenderLayer },
     { "nOnTextureDestroyed",     "(J)V",       (void*) android_view_HardwareLayer_onTextureDestroyed },
 
     { "nPrepare",                "(JIIZ)Z",    (void*) android_view_HardwareLayer_prepare },
@@ -152,8 +128,6 @@
     { "nUpdateSurfaceTexture",   "(J)V",       (void*) android_view_HardwareLayer_updateSurfaceTexture },
     { "nUpdateRenderLayer",      "(JJIIII)V",  (void*) android_view_HardwareLayer_updateRenderLayer },
 
-    { "nFlushChanges",           "(J)Z",       (void*) android_view_HardwareLayer_flushChanges },
-
     { "nGetLayer",               "(J)J",       (void*) android_view_HardwareLayer_getLayer },
     { "nGetTexName",             "(J)I",       (void*) android_view_HardwareLayer_getTexName },
 #endif
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 26022e0..26f8993 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -38,6 +38,11 @@
  */
 #ifdef USE_OPENGL_RENDERER
 
+#define SET_AND_DIRTY(prop, val, dirtyFlag) \
+    (reinterpret_cast<RenderNode*>(renderNodePtr)->mutateStagingProperties().prop(val) \
+        ? (reinterpret_cast<RenderNode*>(renderNodePtr)->setPropertyFieldsDirty(dirtyFlag), true) \
+        : false)
+
 // ----------------------------------------------------------------------------
 // DisplayList view properties
 // ----------------------------------------------------------------------------
@@ -82,235 +87,192 @@
 // RenderProperties - setters
 // ----------------------------------------------------------------------------
 
-static void android_view_RenderNode_setCaching(JNIEnv* env,
+static jboolean android_view_RenderNode_setCaching(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, jboolean caching) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setCaching(caching);
-    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return SET_AND_DIRTY(setCaching, caching, RenderNode::GENERIC);
 }
 
-static void android_view_RenderNode_setStaticMatrix(JNIEnv* env,
+static jboolean android_view_RenderNode_setStaticMatrix(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, jlong matrixPtr) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr);
-    renderNode->mutateStagingProperties().setStaticMatrix(matrix);
-    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return SET_AND_DIRTY(setStaticMatrix, matrix, RenderNode::GENERIC);
 }
 
-static void android_view_RenderNode_setAnimationMatrix(JNIEnv* env,
+static jboolean android_view_RenderNode_setAnimationMatrix(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, jlong matrixPtr) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr);
-    renderNode->mutateStagingProperties().setAnimationMatrix(matrix);
-    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return SET_AND_DIRTY(setAnimationMatrix, matrix, RenderNode::GENERIC);
 }
 
-static void android_view_RenderNode_setClipToBounds(JNIEnv* env,
+static jboolean android_view_RenderNode_setClipToBounds(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, jboolean clipToBounds) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setClipToBounds(clipToBounds);
-    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return SET_AND_DIRTY(setClipToBounds, clipToBounds, RenderNode::GENERIC);
 }
 
-static void android_view_RenderNode_setProjectBackwards(JNIEnv* env,
+static jboolean android_view_RenderNode_setProjectBackwards(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, jboolean shouldProject) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setProjectBackwards(shouldProject);
-    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return SET_AND_DIRTY(setProjectBackwards, shouldProject, RenderNode::GENERIC);
 }
 
-static void android_view_RenderNode_setProjectionReceiver(JNIEnv* env,
+static jboolean android_view_RenderNode_setProjectionReceiver(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, jboolean shouldRecieve) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setProjectionReceiver(shouldRecieve);
-    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return SET_AND_DIRTY(setProjectionReceiver, shouldRecieve, RenderNode::GENERIC);
 }
 
-static void android_view_RenderNode_setOutlineRoundRect(JNIEnv* env,
+static jboolean android_view_RenderNode_setOutlineRoundRect(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, jint left, jint top,
         jint right, jint bottom, jfloat radius) {
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     renderNode->mutateStagingProperties().mutableOutline().setRoundRect(left, top, right, bottom, radius);
     renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return true;
 }
 
-static void android_view_RenderNode_setOutlineConvexPath(JNIEnv* env,
+static jboolean android_view_RenderNode_setOutlineConvexPath(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, jlong outlinePathPtr) {
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     SkPath* outlinePath = reinterpret_cast<SkPath*>(outlinePathPtr);
     renderNode->mutateStagingProperties().mutableOutline().setConvexPath(outlinePath);
     renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return true;
 }
 
-static void android_view_RenderNode_setOutlineEmpty(JNIEnv* env,
+static jboolean android_view_RenderNode_setOutlineEmpty(JNIEnv* env,
         jobject clazz, jlong renderNodePtr) {
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     renderNode->mutateStagingProperties().mutableOutline().setEmpty();
     renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return true;
 }
 
-static void android_view_RenderNode_setClipToOutline(JNIEnv* env,
+static jboolean android_view_RenderNode_setClipToOutline(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, jboolean clipToOutline) {
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     renderNode->mutateStagingProperties().mutableOutline().setShouldClip(clipToOutline);
     renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return true;
 }
 
-static void android_view_RenderNode_setRevealClip(JNIEnv* env,
+static jboolean android_view_RenderNode_setRevealClip(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, jboolean shouldClip, jboolean inverseClip,
         jfloat x, jfloat y, jfloat radius) {
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     renderNode->mutateStagingProperties().mutableRevealClip().set(
             shouldClip, inverseClip, x, y, radius);
     renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return true;
 }
 
-static void android_view_RenderNode_setAlpha(JNIEnv* env,
+static jboolean android_view_RenderNode_setAlpha(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, float alpha) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setAlpha(alpha);
-    renderNode->setPropertyFieldsDirty(RenderNode::ALPHA);
+    return SET_AND_DIRTY(setAlpha, alpha, RenderNode::ALPHA);
 }
 
-static void android_view_RenderNode_setHasOverlappingRendering(JNIEnv* env,
+static jboolean android_view_RenderNode_setHasOverlappingRendering(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, bool hasOverlappingRendering) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setHasOverlappingRendering(hasOverlappingRendering);
-    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return SET_AND_DIRTY(setHasOverlappingRendering, hasOverlappingRendering,
+            RenderNode::GENERIC);
 }
 
-static void android_view_RenderNode_setElevation(JNIEnv* env,
+static jboolean android_view_RenderNode_setElevation(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, float elevation) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setElevation(elevation);
-    renderNode->setPropertyFieldsDirty(RenderNode::Z);
+    return SET_AND_DIRTY(setElevation, elevation, RenderNode::Z);
 }
 
-static void android_view_RenderNode_setTranslationX(JNIEnv* env,
+static jboolean android_view_RenderNode_setTranslationX(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, float tx) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setTranslationX(tx);
-    renderNode->setPropertyFieldsDirty(RenderNode::TRANSLATION_X | RenderNode::X);
+    return SET_AND_DIRTY(setTranslationX, tx, RenderNode::TRANSLATION_X | RenderNode::X);
 }
 
-static void android_view_RenderNode_setTranslationY(JNIEnv* env,
+static jboolean android_view_RenderNode_setTranslationY(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, float ty) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setTranslationY(ty);
-    renderNode->setPropertyFieldsDirty(RenderNode::TRANSLATION_Y | RenderNode::Y);
+    return SET_AND_DIRTY(setTranslationY, ty, RenderNode::TRANSLATION_Y | RenderNode::Y);
 }
 
-static void android_view_RenderNode_setTranslationZ(JNIEnv* env,
+static jboolean android_view_RenderNode_setTranslationZ(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, float tz) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setTranslationZ(tz);
-    renderNode->setPropertyFieldsDirty(RenderNode::TRANSLATION_Z | RenderNode::Z);
+    return SET_AND_DIRTY(setTranslationZ, tz, RenderNode::TRANSLATION_Z | RenderNode::Z);
 }
 
-static void android_view_RenderNode_setRotation(JNIEnv* env,
+static jboolean android_view_RenderNode_setRotation(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, float rotation) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setRotation(rotation);
-    renderNode->setPropertyFieldsDirty(RenderNode::ROTATION);
+    return SET_AND_DIRTY(setRotation, rotation, RenderNode::ROTATION);
 }
 
-static void android_view_RenderNode_setRotationX(JNIEnv* env,
+static jboolean android_view_RenderNode_setRotationX(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, float rx) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setRotationX(rx);
-    renderNode->setPropertyFieldsDirty(RenderNode::ROTATION_X);
+    return SET_AND_DIRTY(setRotationX, rx, RenderNode::ROTATION_X);
 }
 
-static void android_view_RenderNode_setRotationY(JNIEnv* env,
+static jboolean android_view_RenderNode_setRotationY(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, float ry) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setRotationY(ry);
-    renderNode->setPropertyFieldsDirty(RenderNode::ROTATION_Y);
+    return SET_AND_DIRTY(setRotationY, ry, RenderNode::ROTATION_Y);
 }
 
-static void android_view_RenderNode_setScaleX(JNIEnv* env,
+static jboolean android_view_RenderNode_setScaleX(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, float sx) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setScaleX(sx);
-    renderNode->setPropertyFieldsDirty(RenderNode::SCALE_X);
+    return SET_AND_DIRTY(setScaleX, sx, RenderNode::SCALE_X);
 }
 
-static void android_view_RenderNode_setScaleY(JNIEnv* env,
+static jboolean android_view_RenderNode_setScaleY(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, float sy) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setScaleY(sy);
-    renderNode->setPropertyFieldsDirty(RenderNode::SCALE_Y);
+    return SET_AND_DIRTY(setScaleY, sy, RenderNode::SCALE_Y);
 }
 
-static void android_view_RenderNode_setPivotX(JNIEnv* env,
+static jboolean android_view_RenderNode_setPivotX(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, float px) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setPivotX(px);
-    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return SET_AND_DIRTY(setPivotX, px, RenderNode::GENERIC);
 }
 
-static void android_view_RenderNode_setPivotY(JNIEnv* env,
+static jboolean android_view_RenderNode_setPivotY(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, float py) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setPivotY(py);
-    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return SET_AND_DIRTY(setPivotY, py, RenderNode::GENERIC);
 }
 
-static void android_view_RenderNode_setCameraDistance(JNIEnv* env,
+static jboolean android_view_RenderNode_setCameraDistance(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, float distance) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setCameraDistance(distance);
-    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return SET_AND_DIRTY(setCameraDistance, distance, RenderNode::GENERIC);
 }
 
-static void android_view_RenderNode_setLeft(JNIEnv* env,
+static jboolean android_view_RenderNode_setLeft(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, int left) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setLeft(left);
-    renderNode->setPropertyFieldsDirty(RenderNode::X);
+    return SET_AND_DIRTY(setLeft, left, RenderNode::X);
 }
 
-static void android_view_RenderNode_setTop(JNIEnv* env,
+static jboolean android_view_RenderNode_setTop(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, int top) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setTop(top);
-    renderNode->setPropertyFieldsDirty(RenderNode::Y);
+    return SET_AND_DIRTY(setTop, top, RenderNode::Y);
 }
 
-static void android_view_RenderNode_setRight(JNIEnv* env,
+static jboolean android_view_RenderNode_setRight(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, int right) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setRight(right);
-    renderNode->setPropertyFieldsDirty(RenderNode::X);
+    return SET_AND_DIRTY(setRight, right, RenderNode::X);
 }
 
-static void android_view_RenderNode_setBottom(JNIEnv* env,
+static jboolean android_view_RenderNode_setBottom(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, int bottom) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setBottom(bottom);
-    renderNode->setPropertyFieldsDirty(RenderNode::Y);
+    return SET_AND_DIRTY(setBottom, bottom, RenderNode::Y);
 }
 
-static void android_view_RenderNode_setLeftTopRightBottom(JNIEnv* env,
+static jboolean android_view_RenderNode_setLeftTopRightBottom(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, int left, int top,
         int right, int bottom) {
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom);
-    renderNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+    if (renderNode->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom)) {
+        renderNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        return true;
+    }
+    return false;
 }
 
-static void android_view_RenderNode_offsetLeftAndRight(JNIEnv* env,
+static jboolean android_view_RenderNode_offsetLeftAndRight(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, float offset) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().offsetLeftRight(offset);
-    renderNode->setPropertyFieldsDirty(RenderNode::X);
+    return SET_AND_DIRTY(offsetLeftRight, offset, RenderNode::X);
 }
 
-static void android_view_RenderNode_offsetTopAndBottom(JNIEnv* env,
+static jboolean android_view_RenderNode_offsetTopAndBottom(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, float offset) {
-    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
-    renderNode->mutateStagingProperties().offsetTopBottom(offset);
-    renderNode->setPropertyFieldsDirty(RenderNode::Y);
+    return SET_AND_DIRTY(offsetTopBottom, offset, RenderNode::Y);
 }
 
 // ----------------------------------------------------------------------------
@@ -513,41 +475,41 @@
     { "nOutput",               "(J)V",  (void*) android_view_RenderNode_output },
     { "nGetDebugSize",         "(J)I",  (void*) android_view_RenderNode_getDebugSize },
 
-    { "nSetCaching",           "(JZ)V",  (void*) android_view_RenderNode_setCaching },
-    { "nSetStaticMatrix",      "(JJ)V",  (void*) android_view_RenderNode_setStaticMatrix },
-    { "nSetAnimationMatrix",   "(JJ)V",  (void*) android_view_RenderNode_setAnimationMatrix },
-    { "nSetClipToBounds",      "(JZ)V",  (void*) android_view_RenderNode_setClipToBounds },
-    { "nSetProjectBackwards",  "(JZ)V",  (void*) android_view_RenderNode_setProjectBackwards },
-    { "nSetProjectionReceiver","(JZ)V",  (void*) android_view_RenderNode_setProjectionReceiver },
+    { "nSetCaching",           "(JZ)Z",  (void*) android_view_RenderNode_setCaching },
+    { "nSetStaticMatrix",      "(JJ)Z",  (void*) android_view_RenderNode_setStaticMatrix },
+    { "nSetAnimationMatrix",   "(JJ)Z",  (void*) android_view_RenderNode_setAnimationMatrix },
+    { "nSetClipToBounds",      "(JZ)Z",  (void*) android_view_RenderNode_setClipToBounds },
+    { "nSetProjectBackwards",  "(JZ)Z",  (void*) android_view_RenderNode_setProjectBackwards },
+    { "nSetProjectionReceiver","(JZ)Z",  (void*) android_view_RenderNode_setProjectionReceiver },
 
-    { "nSetOutlineRoundRect",  "(JIIIIF)V", (void*) android_view_RenderNode_setOutlineRoundRect },
-    { "nSetOutlineConvexPath", "(JJ)V",  (void*) android_view_RenderNode_setOutlineConvexPath },
-    { "nSetOutlineEmpty",      "(J)V",   (void*) android_view_RenderNode_setOutlineEmpty },
-    { "nSetClipToOutline",     "(JZ)V",  (void*) android_view_RenderNode_setClipToOutline },
-    { "nSetRevealClip",        "(JZZFFF)V", (void*) android_view_RenderNode_setRevealClip },
+    { "nSetOutlineRoundRect",  "(JIIIIF)Z", (void*) android_view_RenderNode_setOutlineRoundRect },
+    { "nSetOutlineConvexPath", "(JJ)Z",  (void*) android_view_RenderNode_setOutlineConvexPath },
+    { "nSetOutlineEmpty",      "(J)Z",   (void*) android_view_RenderNode_setOutlineEmpty },
+    { "nSetClipToOutline",     "(JZ)Z",  (void*) android_view_RenderNode_setClipToOutline },
+    { "nSetRevealClip",        "(JZZFFF)Z", (void*) android_view_RenderNode_setRevealClip },
 
-    { "nSetAlpha",             "(JF)V",  (void*) android_view_RenderNode_setAlpha },
-    { "nSetHasOverlappingRendering", "(JZ)V",
+    { "nSetAlpha",             "(JF)Z",  (void*) android_view_RenderNode_setAlpha },
+    { "nSetHasOverlappingRendering", "(JZ)Z",
             (void*) android_view_RenderNode_setHasOverlappingRendering },
-    { "nSetElevation",         "(JF)V",  (void*) android_view_RenderNode_setElevation },
-    { "nSetTranslationX",      "(JF)V",  (void*) android_view_RenderNode_setTranslationX },
-    { "nSetTranslationY",      "(JF)V",  (void*) android_view_RenderNode_setTranslationY },
-    { "nSetTranslationZ",      "(JF)V",  (void*) android_view_RenderNode_setTranslationZ },
-    { "nSetRotation",          "(JF)V",  (void*) android_view_RenderNode_setRotation },
-    { "nSetRotationX",         "(JF)V",  (void*) android_view_RenderNode_setRotationX },
-    { "nSetRotationY",         "(JF)V",  (void*) android_view_RenderNode_setRotationY },
-    { "nSetScaleX",            "(JF)V",  (void*) android_view_RenderNode_setScaleX },
-    { "nSetScaleY",            "(JF)V",  (void*) android_view_RenderNode_setScaleY },
-    { "nSetPivotX",            "(JF)V",  (void*) android_view_RenderNode_setPivotX },
-    { "nSetPivotY",            "(JF)V",  (void*) android_view_RenderNode_setPivotY },
-    { "nSetCameraDistance",    "(JF)V",  (void*) android_view_RenderNode_setCameraDistance },
-    { "nSetLeft",              "(JI)V",  (void*) android_view_RenderNode_setLeft },
-    { "nSetTop",               "(JI)V",  (void*) android_view_RenderNode_setTop },
-    { "nSetRight",             "(JI)V",  (void*) android_view_RenderNode_setRight },
-    { "nSetBottom",            "(JI)V",  (void*) android_view_RenderNode_setBottom },
-    { "nSetLeftTopRightBottom","(JIIII)V", (void*) android_view_RenderNode_setLeftTopRightBottom },
-    { "nOffsetLeftAndRight",   "(JF)V",  (void*) android_view_RenderNode_offsetLeftAndRight },
-    { "nOffsetTopAndBottom",   "(JF)V",  (void*) android_view_RenderNode_offsetTopAndBottom },
+    { "nSetElevation",         "(JF)Z",  (void*) android_view_RenderNode_setElevation },
+    { "nSetTranslationX",      "(JF)Z",  (void*) android_view_RenderNode_setTranslationX },
+    { "nSetTranslationY",      "(JF)Z",  (void*) android_view_RenderNode_setTranslationY },
+    { "nSetTranslationZ",      "(JF)Z",  (void*) android_view_RenderNode_setTranslationZ },
+    { "nSetRotation",          "(JF)Z",  (void*) android_view_RenderNode_setRotation },
+    { "nSetRotationX",         "(JF)Z",  (void*) android_view_RenderNode_setRotationX },
+    { "nSetRotationY",         "(JF)Z",  (void*) android_view_RenderNode_setRotationY },
+    { "nSetScaleX",            "(JF)Z",  (void*) android_view_RenderNode_setScaleX },
+    { "nSetScaleY",            "(JF)Z",  (void*) android_view_RenderNode_setScaleY },
+    { "nSetPivotX",            "(JF)Z",  (void*) android_view_RenderNode_setPivotX },
+    { "nSetPivotY",            "(JF)Z",  (void*) android_view_RenderNode_setPivotY },
+    { "nSetCameraDistance",    "(JF)Z",  (void*) android_view_RenderNode_setCameraDistance },
+    { "nSetLeft",              "(JI)Z",  (void*) android_view_RenderNode_setLeft },
+    { "nSetTop",               "(JI)Z",  (void*) android_view_RenderNode_setTop },
+    { "nSetRight",             "(JI)Z",  (void*) android_view_RenderNode_setRight },
+    { "nSetBottom",            "(JI)Z",  (void*) android_view_RenderNode_setBottom },
+    { "nSetLeftTopRightBottom","(JIIII)Z", (void*) android_view_RenderNode_setLeftTopRightBottom },
+    { "nOffsetLeftAndRight",   "(JF)Z",  (void*) android_view_RenderNode_offsetLeftAndRight },
+    { "nOffsetTopAndBottom",   "(JF)Z",  (void*) android_view_RenderNode_offsetTopAndBottom },
 
     { "nHasOverlappingRendering", "(J)Z",  (void*) android_view_RenderNode_hasOverlappingRendering },
     { "nGetClipToOutline",        "(J)Z",  (void*) android_view_RenderNode_getClipToOutline },
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 6c9d060..3d14aaf 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -70,7 +70,7 @@
 
 static struct {
     jfieldID mSurfaceFormat;
-    jmethodID safeCanvasSwap;
+    jmethodID setNativeBitmap;
 } gCanvasClassInfo;
 
 // ----------------------------------------------------------------------------
@@ -95,6 +95,7 @@
                 env->GetLongField(surfaceObj, gSurfaceClassInfo.mNativeObject));
         env->MonitorExit(lock);
     }
+    env->DeleteLocalRef(lock);
     return sur;
 }
 
@@ -232,10 +233,11 @@
         bitmap.setPixels(NULL);
     }
 
-    SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap));
-    env->CallVoidMethod(canvasObj, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false);
+    env->CallVoidMethod(canvasObj, gCanvasClassInfo.setNativeBitmap,
+                        reinterpret_cast<jlong>(&bitmap));
 
     if (dirtyRectPtr) {
+        SkCanvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
         nativeCanvas->clipRect( SkRect::Make(reinterpret_cast<const SkIRect&>(dirtyRect)) );
     }
 
@@ -262,8 +264,7 @@
     }
 
     // detach the canvas from the surface
-    SkCanvas* nativeCanvas = SkNEW(SkCanvas);
-    env->CallVoidMethod(canvasObj, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false);
+    env->CallVoidMethod(canvasObj, gCanvasClassInfo.setNativeBitmap, (jlong)0);
 
     // unlock surface
     status_t err = surface->unlockAndPost();
@@ -375,7 +376,7 @@
 
     clazz = env->FindClass("android/graphics/Canvas");
     gCanvasClassInfo.mSurfaceFormat = env->GetFieldID(clazz, "mSurfaceFormat", "I");
-    gCanvasClassInfo.safeCanvasSwap = env->GetMethodID(clazz, "safeCanvasSwap", "(JZ)V");
+    gCanvasClassInfo.setNativeBitmap = env->GetMethodID(clazz, "setNativeBitmap", "(J)V");
 
     clazz = env->FindClass("android/graphics/Rect");
     gRectClassInfo.left = env->GetFieldID(clazz, "left", "I");
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 5a935a9..c0d5221 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -42,6 +42,8 @@
 
 #include <ScopedUtfChars.h>
 
+#include "SkTemplates.h"
+
 // ----------------------------------------------------------------------------
 
 namespace android {
@@ -61,6 +63,13 @@
     jfieldID secure;
 } gPhysicalDisplayInfoClassInfo;
 
+static struct {
+    jfieldID bottom;
+    jfieldID left;
+    jfieldID right;
+    jfieldID top;
+} gRectClassInfo;
+
 // Implements SkMallocPixelRef::ReleaseProc, to delete the screenshot on unref.
 void DeleteScreenshot(void* addr, void* context) {
     SkASSERT(addr == ((ScreenshotClient*) context)->getPixels());
@@ -104,28 +113,34 @@
     ctrl->decStrong((void *)nativeCreate);
 }
 
-static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, jobject displayTokenObj,
-        jint width, jint height, jint minLayer, jint maxLayer, bool allLayers,
-        bool useIdentityTransform) {
+static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz,
+        jobject displayTokenObj, jobject sourceCropObj, jint width, jint height,
+        jint minLayer, jint maxLayer, bool allLayers, bool useIdentityTransform) {
     sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj);
     if (displayToken == NULL) {
         return NULL;
     }
 
-    ScreenshotClient* screenshot = new ScreenshotClient();
+    int left = env->GetIntField(sourceCropObj, gRectClassInfo.left);
+    int top = env->GetIntField(sourceCropObj, gRectClassInfo.top);
+    int right = env->GetIntField(sourceCropObj, gRectClassInfo.right);
+    int bottom = env->GetIntField(sourceCropObj, gRectClassInfo.bottom);
+    Rect sourceCrop(left, top, right, bottom);
+
+    SkAutoTDelete<ScreenshotClient> screenshot(new ScreenshotClient());
     status_t res;
     if (width > 0 && height > 0) {
         if (allLayers) {
-            res = screenshot->update(displayToken, width, height, useIdentityTransform);
-        } else {
-            res = screenshot->update(displayToken, width, height, minLayer, maxLayer,
+            res = screenshot->update(displayToken, sourceCrop, width, height,
                     useIdentityTransform);
+        } else {
+            res = screenshot->update(displayToken, sourceCrop, width, height,
+                    minLayer, maxLayer, useIdentityTransform);
         }
     } else {
-        res = screenshot->update(displayToken, useIdentityTransform);
+        res = screenshot->update(displayToken, sourceCrop, useIdentityTransform);
     }
     if (res != NO_ERROR) {
-        delete screenshot;
         return NULL;
     }
 
@@ -150,7 +165,6 @@
             break;
         }
         default: {
-            delete screenshot;
             return NULL;
         }
     }
@@ -164,7 +178,7 @@
         // takes ownership of ScreenshotClient
         SkMallocPixelRef* pixels = SkMallocPixelRef::NewWithProc(screenshotInfo,
                 (size_t) rowBytes, NULL, (void*) screenshot->getPixels(), &DeleteScreenshot,
-                (void*) screenshot);
+                (void*) (screenshot.detach()));
         pixels->setImmutable();
         bitmap->setPixelRef(pixels)->unref();
         bitmap->lockPixels();
@@ -174,20 +188,25 @@
             GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL);
 }
 
-static void nativeScreenshot(JNIEnv* env, jclass clazz,
-        jobject displayTokenObj, jobject surfaceObj,
-        jint width, jint height, jint minLayer, jint maxLayer, bool allLayers,
-        bool useIdentityTransform) {
+static void nativeScreenshot(JNIEnv* env, jclass clazz, jobject displayTokenObj,
+        jobject surfaceObj, jobject sourceCropObj, jint width, jint height,
+        jint minLayer, jint maxLayer, bool allLayers, bool useIdentityTransform) {
     sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj);
     if (displayToken != NULL) {
         sp<Surface> consumer = android_view_Surface_getSurface(env, surfaceObj);
         if (consumer != NULL) {
+            int left = env->GetIntField(sourceCropObj, gRectClassInfo.left);
+            int top = env->GetIntField(sourceCropObj, gRectClassInfo.top);
+            int right = env->GetIntField(sourceCropObj, gRectClassInfo.right);
+            int bottom = env->GetIntField(sourceCropObj, gRectClassInfo.bottom);
+            Rect sourceCrop(left, top, right, bottom);
+
             if (allLayers) {
                 minLayer = 0;
                 maxLayer = -1;
             }
-            ScreenshotClient::capture(
-                    displayToken, consumer->getIGraphicBufferProducer(),
+            ScreenshotClient::capture(displayToken,
+                    consumer->getIGraphicBufferProducer(), sourceCrop,
                     width, height, uint32_t(minLayer), uint32_t(maxLayer),
                     useIdentityTransform);
         }
@@ -393,20 +412,12 @@
     return err == NO_ERROR ? JNI_TRUE : JNI_FALSE;
 }
 
-static void nativeBlankDisplay(JNIEnv* env, jclass clazz, jobject tokenObj) {
+static void nativeSetDisplayPowerMode(JNIEnv* env, jclass clazz, jobject tokenObj, jint mode) {
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == NULL) return;
 
-    ALOGD_IF_SLOW(100, "Excessive delay in blankDisplay() while turning screen off");
-    SurfaceComposerClient::blankDisplay(token);
-}
-
-static void nativeUnblankDisplay(JNIEnv* env, jclass clazz, jobject tokenObj) {
-    sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
-    if (token == NULL) return;
-
-    ALOGD_IF_SLOW(100, "Excessive delay in unblankDisplay() while turning screen on");
-    SurfaceComposerClient::unblankDisplay(token);
+    ALOGD_IF_SLOW(100, "Excessive delay in setPowerMode()");
+    SurfaceComposerClient::setDisplayPowerMode(token, mode);
 }
 
 static jboolean nativeClearContentFrameStats(JNIEnv* env, jclass clazz, jlong nativeObject) {
@@ -563,9 +574,9 @@
             (void*)nativeRelease },
     {"nativeDestroy", "(J)V",
             (void*)nativeDestroy },
-    {"nativeScreenshot", "(Landroid/os/IBinder;IIIIZZ)Landroid/graphics/Bitmap;",
+    {"nativeScreenshot", "(Landroid/os/IBinder;Landroid/graphics/Rect;IIIIZZ)Landroid/graphics/Bitmap;",
             (void*)nativeScreenshotBitmap },
-    {"nativeScreenshot", "(Landroid/os/IBinder;Landroid/view/Surface;IIIIZZ)V",
+    {"nativeScreenshot", "(Landroid/os/IBinder;Landroid/view/Surface;Landroid/graphics/Rect;IIIIZZ)V",
             (void*)nativeScreenshot },
     {"nativeOpenTransaction", "()V",
             (void*)nativeOpenTransaction },
@@ -609,10 +620,6 @@
             (void*)nativeGetActiveConfig },
     {"nativeSetActiveConfig", "(Landroid/os/IBinder;I)Z",
             (void*)nativeSetActiveConfig },
-    {"nativeBlankDisplay", "(Landroid/os/IBinder;)V",
-            (void*)nativeBlankDisplay },
-    {"nativeUnblankDisplay", "(Landroid/os/IBinder;)V",
-            (void*)nativeUnblankDisplay },
     {"nativeClearContentFrameStats", "(J)Z",
             (void*)nativeClearContentFrameStats },
     {"nativeGetContentFrameStats", "(JLandroid/view/WindowContentFrameStats;)Z",
@@ -621,6 +628,8 @@
             (void*)nativeClearAnimationFrameStats },
     {"nativeGetAnimationFrameStats", "(Landroid/view/WindowAnimationFrameStats;)Z",
             (void*)nativeGetAnimationFrameStats },
+    {"nativeSetDisplayPowerMode", "(Landroid/os/IBinder;I)V",
+            (void*)nativeSetDisplayPowerMode },
 };
 
 int register_android_view_SurfaceControl(JNIEnv* env)
@@ -640,6 +649,12 @@
     gPhysicalDisplayInfoClassInfo.yDpi = env->GetFieldID(clazz, "yDpi", "F");
     gPhysicalDisplayInfoClassInfo.secure = env->GetFieldID(clazz, "secure", "Z");
 
+    jclass rectClazz = env->FindClass("android/graphics/Rect");
+    gRectClassInfo.bottom = env->GetFieldID(rectClazz, "bottom", "I");
+    gRectClassInfo.left = env->GetFieldID(rectClazz, "left", "I");
+    gRectClassInfo.right = env->GetFieldID(rectClazz, "right", "I");
+    gRectClassInfo.top = env->GetFieldID(rectClazz, "top", "I");
+
     jclass frameStatsClazz = env->FindClass("android/view/FrameStats");
     jfieldID undefined_time_nano_field =  env->GetStaticFieldID(frameStatsClazz, "UNDEFINED_TIME_NANO", "J");
     nsecs_t undefined_time_nano = env->GetStaticLongField(frameStatsClazz, undefined_time_nano_field);
diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp
index 9258543..c1ab515 100644
--- a/core/jni/android_view_TextureView.cpp
+++ b/core/jni/android_view_TextureView.cpp
@@ -29,6 +29,8 @@
 #include <SkCanvas.h>
 #include <SkImage.h>
 
+#include "android/graphics/GraphicsJNI.h"
+
 namespace android {
 
 // ----------------------------------------------------------------------------
@@ -45,7 +47,7 @@
 
 static struct {
     jfieldID mSurfaceFormat;
-    jmethodID safeCanvasSwap;
+    jmethodID setNativeBitmap;
 } gCanvasClassInfo;
 
 static struct {
@@ -159,12 +161,11 @@
     }
 
     SET_INT(canvas, gCanvasClassInfo.mSurfaceFormat, buffer.format);
-
-    SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap));
-    INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false);
+    INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, reinterpret_cast<jlong>(&bitmap));
 
     SkRect clipRect;
     clipRect.set(rect.left, rect.top, rect.right, rect.bottom);
+    SkCanvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas);
     nativeCanvas->clipRect(clipRect);
 
     if (dirtyRect) {
@@ -178,8 +179,7 @@
 static void android_view_TextureView_unlockCanvasAndPost(JNIEnv* env, jobject,
         jlong nativeWindow, jobject canvas) {
 
-    SkCanvas* nativeCanvas = SkNEW(SkCanvas);
-    INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false);
+    INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, (jlong)0);
 
     if (nativeWindow) {
         sp<ANativeWindow> window((ANativeWindow*) nativeWindow);
@@ -228,7 +228,7 @@
 
     FIND_CLASS(clazz, "android/graphics/Canvas");
     GET_FIELD_ID(gCanvasClassInfo.mSurfaceFormat, clazz, "mSurfaceFormat", "I");
-    GET_METHOD_ID(gCanvasClassInfo.safeCanvasSwap, clazz, "safeCanvasSwap", "(JZ)V");
+    GET_METHOD_ID(gCanvasClassInfo.setNativeBitmap, clazz, "setNativeBitmap", "(J)V");
 
     FIND_CLASS(clazz, "android/view/TextureView");
     GET_FIELD_ID(gTextureViewClassInfo.nativeWindow, clazz, "mNativeWindow", "J");
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 1397131..815c4a7 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -22,6 +22,10 @@
 #include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <EGL/egl_cache.h>
+
 #include <utils/StrongPointer.h>
 #include <android_runtime/android_view_Surface.h>
 #include <system/window.h>
@@ -146,6 +150,13 @@
         }
     }
 
+protected:
+    virtual void damageSelf(TreeInfo& info) {
+        // Intentionally a no-op. As RootRenderNode gets a new DisplayListData
+        // every frame this would result in every draw push being a full inval,
+        // which is wrong. Only RootRenderNode has this issue.
+    }
+
 private:
     sp<Looper> mLooper;
     std::vector<OnFinishedEvent> mOnFinishedEvents;
@@ -238,11 +249,9 @@
 }
 
 static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
-        jlong proxyPtr, jlong frameTimeNanos, jlong recordDuration, jfloat density,
-        jint dirtyLeft, jint dirtyTop, jint dirtyRight, jint dirtyBottom) {
+        jlong proxyPtr, jlong frameTimeNanos, jlong recordDuration, jfloat density) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
-    return proxy->syncAndDrawFrame(frameTimeNanos, recordDuration, density,
-            dirtyLeft, dirtyTop, dirtyRight, dirtyBottom);
+    return proxy->syncAndDrawFrame(frameTimeNanos, recordDuration, density);
 }
 
 static void android_view_ThreadedRenderer_destroyCanvasAndSurface(JNIEnv* env, jobject clazz,
@@ -329,6 +338,18 @@
 #endif
 
 // ----------------------------------------------------------------------------
+// Shaders
+// ----------------------------------------------------------------------------
+
+static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz,
+        jstring diskCachePath) {
+
+    const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL);
+    egl_cache_t::get()->setCacheFilename(cacheArray);
+    env->ReleaseStringUTFChars(diskCachePath, cacheArray);
+}
+
+// ----------------------------------------------------------------------------
 // JNI Glue
 // ----------------------------------------------------------------------------
 
@@ -347,7 +368,7 @@
     { "nPauseSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_pauseSurface },
     { "nSetup", "(JIIFFFF)V", (void*) android_view_ThreadedRenderer_setup },
     { "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque },
-    { "nSyncAndDrawFrame", "(JJJFIIII)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
+    { "nSyncAndDrawFrame", "(JJJF)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
     { "nDestroyCanvasAndSurface", "(J)V", (void*) android_view_ThreadedRenderer_destroyCanvasAndSurface },
     { "nInvokeFunctor", "(JJZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor },
     { "nRunWithGlContext", "(JLjava/lang/Runnable;)V", (void*) android_view_ThreadedRenderer_runWithGlContext },
@@ -361,6 +382,8 @@
     { "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending },
     { "nDumpProfileInfo", "(JLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo },
 #endif
+    { "setupShadersDiskCache", "(Ljava/lang/String;)V",
+                (void*) android_view_ThreadedRenderer_setupShadersDiskCache },
 };
 
 int register_android_view_ThreadedRenderer(JNIEnv* env) {
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 230658f..e55e4ea 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -46,6 +46,9 @@
 #define LIB_SUFFIX ".so"
 #define LIB_SUFFIX_LEN (sizeof(LIB_SUFFIX) - 1)
 
+#define RS_BITCODE_SUFFIX ".bc"
+#define RS_BITCODE_SUFFIX_LEN (sizeof(RS_BITCODE_SUFFIX) -1)
+
 #define GDBSERVER "gdbserver"
 #define GDBSERVER_LEN (sizeof(GDBSERVER) - 1)
 
@@ -486,6 +489,42 @@
     return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch);
 }
 
+enum bitcode_scan_result_t {
+  APK_SCAN_ERROR = -1,
+  NO_BITCODE_PRESENT = 0,
+  BITCODE_PRESENT = 1,
+};
+
+static jint
+com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode(JNIEnv *env, jclass clazz,
+        jlong apkHandle) {
+    ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
+    void* cookie = NULL;
+    if (!zipFile->startIteration(&cookie)) {
+        return APK_SCAN_ERROR;
+    }
+
+    char fileName[PATH_MAX];
+    ZipEntryRO next = NULL;
+    while ((next = zipFile->nextEntry(cookie)) != NULL) {
+        if (zipFile->getEntryFileName(next, fileName, sizeof(fileName))) {
+            continue;
+        }
+
+        const size_t fileNameLen = strlen(fileName);
+        const char* lastSlash = strrchr(fileName, '/');
+        const char* baseName = (lastSlash == NULL) ? fileName : fileName + 1;
+        if (!strncmp(fileName + fileNameLen - RS_BITCODE_SUFFIX_LEN, RS_BITCODE_SUFFIX,
+                     RS_BITCODE_SUFFIX_LEN) && isFilenameSafe(baseName)) {
+            zipFile->endIteration(cookie);
+            return BITCODE_PRESENT;
+        }
+    }
+
+    zipFile->endIteration(cookie);
+    return NO_BITCODE_PRESENT;
+}
+
 static jlong
 com_android_internal_content_NativeLibraryHelper_openApk(JNIEnv *env, jclass, jstring apkPath)
 {
@@ -517,6 +556,8 @@
     {"nativeFindSupportedAbi",
             "(J[Ljava/lang/String;)I",
             (void *)com_android_internal_content_NativeLibraryHelper_findSupportedAbi},
+    {"hasRenderscriptBitcode", "(J)I",
+            (void *)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode},
 };
 
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c5fd83d..3067cdd0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -260,6 +260,12 @@
     <protected-broadcast
         android:name="com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION" />
 
+    <!-- Defined in RestrictionsManager -->
+    <protected-broadcast
+        android:name="android.intent.action.PERMISSION_RESPONSE_RECEIVED" />
+    <!-- Defined in RestrictionsManager -->
+    <protected-broadcast android:name="android.intent.action.REQUEST_PERMISSION" />
+
     <!-- ====================================== -->
     <!-- Permissions for things that cost money -->
     <!-- ====================================== -->
@@ -1007,6 +1013,14 @@
         android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
         android:protectionLevel="signature" />
 
+    <!-- Allows an application to communicate with a SIM card using logical
+         channels. -->
+    <permission android:name="android.permission.SIM_COMMUNICATION"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:label="@string/permlab_sim_communication"
+        android:description="@string/permdesc_sim_communication"
+        android:protectionLevel="dangerous" />
+
     <!-- @SystemApi Allows TvInputService to access underlying TV input hardware such as
          built-in tuners and HDMI-in's.
          @hide This should only be used by OEM's TvInputService's.
@@ -1746,10 +1760,8 @@
         android:label="@string/permlab_manageCaCertificates"
         android:description="@string/permdesc_manageCaCertificates" />
 
-    <!-- @SystemApi Allows an application to do certain operations
-         needed for interacting with the recovery (system update)
-         system.
-         @hide -->
+    <!-- @SystemApi Allows an application to do certain operations needed for
+         interacting with the recovery (system update) system. -->
     <permission android:name="android.permission.RECOVERY"
         android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
         android:protectionLevel="signature|system"
@@ -2059,6 +2071,14 @@
         android:description="@string/permdesc_bindVoiceInteraction"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by hotword enrollment application,
+         to ensure that only the system can interact with it.
+         @hide <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES"
+        android:label="@string/permlab_manageVoiceKeyphrases"
+        android:description="@string/permdesc_manageVoiceKeyphrases"
+        android:protectionLevel="signature|system" />
+
     <!-- Must be required by a {@link com.android.media.remotedisplay.RemoteDisplayProvider},
          to ensure that only the system can bind to it.
          @hide -->
@@ -2625,8 +2645,7 @@
 
     <!-- Must be required by an {@link
          android.service.trust.TrustAgentService},
-         to ensure that only the system can bind to it.
-         @hide -->
+         to ensure that only the system can bind to it. -->
     <permission android:name="android.permission.BIND_TRUST_AGENT"
                 android:protectionLevel="signature"
                 android:label="@string/permlab_bind_trust_agent_service"
@@ -2795,7 +2814,7 @@
         </activity>
 
         <activity android:name="com.android.internal.app.RestrictionsPinActivity"
-                android:theme="@style/Theme.Holo.Dialog.Alert"
+                android:theme="@style/Theme.Material.Light.Dialog.Alert"
                 android:excludeFromRecents="true"
                 android:windowSoftInputMode="adjustPan"
                 android:process=":ui">
diff --git a/core/res/assets/images/android-logo-mask.png b/core/res/assets/images/android-logo-mask.png
index 1e8ab95..3078b28 100644
--- a/core/res/assets/images/android-logo-mask.png
+++ b/core/res/assets/images/android-logo-mask.png
Binary files differ
diff --git a/core/res/assets/images/android-logo-shine.png b/core/res/assets/images/android-logo-shine.png
index 60144f1..add6796 100644
--- a/core/res/assets/images/android-logo-shine.png
+++ b/core/res/assets/images/android-logo-shine.png
Binary files differ
diff --git a/core/res/res/anim/slide_in_micro.xml b/core/res/res/anim/slide_in_micro.xml
new file mode 100644
index 0000000..6320e80
--- /dev/null
+++ b/core/res/res/anim/slide_in_micro.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/slide_in_micro.xml
+**
+** Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate android:fromXDelta="100%p" android:toXDelta="0"
+               android:duration="@android:integer/config_mediumAnimTime"/>
+    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+           android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+           android:duration="@android:integer/config_mediumAnimTime" />
+</set>
diff --git a/core/res/res/anim/slide_out_micro.xml b/core/res/res/anim/slide_out_micro.xml
new file mode 100644
index 0000000..4cb6df0
--- /dev/null
+++ b/core/res/res/anim/slide_out_micro.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/slide_out_micro.xml
+**
+** Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate android:fromXDelta="0" android:toXDelta="100%p"
+               android:duration="@android:integer/config_mediumAnimTime"/>
+    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+           android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+           android:duration="@android:integer/config_mediumAnimTime" />
+</set>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 07449ab..bd92c53 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Veiligmodus"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android-stelsel"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Persoonlik"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Werk"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Persoonlike programme"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android vir Werk"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Dienste wat jou geld kos"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Doen dinge wat jou geld kan kos."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Jou boodskappe"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Laat die program toe om globale klankinstellings soos volume en watter luidspreker vir uitvoer gebruik word, te verander."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"neem klank op"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Laat die program toe om klank met die mikrofoon op te neem. Hierdie toestemming laat die program toe om klank te eniger tyd, sonder jou bevestiging, op te neem."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"sim-kommunikasie"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Laat die program toe om bevele na die SIM te stuur. Dit is baie gevaarlik."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"neem foto\'s en video\'s"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Laat die program toe om foto\'s en video\'s met die kamera te neem. Hierdie toestemming laat die program toe om die kamera te eniger tyd sonder jou bevestiging te gebruik."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"deaktiveer LED wat oordrag aandui wanneer kamera gebruik word"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 67406cb..b2b93ab 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"የሚያስተማምን ሁነታ"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android ስርዓት"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"የግል"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"ስራ"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"የግል መተግበሪያዎች"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android ለስራ"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"ገንዘብ የሚያስወጥዎ አገልግሎቶች"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"ገንዘብ የሚያስወጡህን ነገሮች አድርግ።"</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"መልዕክቶችዎ"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"መተግበሪያው አንደ የድምጽ መጠን እና ለውጽአት የትኛውን የድምጽ ማጉያ ጥቅም ላይ እንደዋለ የመሳሰሉ ሁለንተናዊ የድምጽ ቅንብሮችን እንዲያስተካክል ይፈቅድለታል።"</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"ኦዲዮ ይቅዱ"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"መተግበሪያው ድምጽን በማይክሮፎን እንዲቀዳ ይፈቅድለታል። ይህ ፈቃድ መተግበሪያው ያላንተ ማረጋገጫ በማንኛውም ጊዜ ድምጽ እንዲቀዳ ይፈቅድለታል።"</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"የሲም ግንኙነት"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"መተግበሪያው ትዕዛዞችን ወደ ሲሙ እንዲልክ ያስችለዋል። ይሄ በጣማ አደገኛ ነው።"</string>
     <string name="permlab_camera" msgid="3616391919559751192">"ፎቶዎች እና ቪዲዮዎች ያንሱ"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"መተግበሪያው በካሜራው ፎቶዎችንና ቪዲዮዎችን እንዲያነሳ ይፈቅድለታል። ይህ ፈቃድ መተግበሪያው ካሜራውን በማንኛውም ጊዜ ያላንተ ማረጋገጫ እንዲጠቀም ይፈቅድለታል።"</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"ካሜራው ስራ ላይ ሲሆን የማስተላለፍ አመልካች ኤል ኢ ዲን ያሰናክሉ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index f8e5272..e5a3f39 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"الوضع الآمن"</string>
     <string name="android_system_label" msgid="6577375335728551336">"‏نظام Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"شخصي"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"عمل"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"التطبيقات الشخصية"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"‏Android للعمل"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"الخدمات التي تكلفك المال"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"يمكنك تنفيذ إجراءات يمكن أن تكلفك مالاً."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"رسائلك"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"للسماح للتطبيق بتعديل إعدادات الصوت العامة مثل مستوى الصوت وأي السماعات يتم استخدامها للاستماع."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"تسجيل الصوت"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"للسماح للتطبيق بتسجيل الصوت باستخدام الميكروفون. ويتيح هذا الإذن للتطبيق تسجيل الصوت في أي وقت وبدون موافقة منك."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"‏اتصالات SIM"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"‏السماح للتطبيق بإرسال أوامر إلى بطاقة SIM. وهذا أمر بالغ الخطورة."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"التقاط صور ومقاطع فيديو"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"للسماح للتطبيق بالتقاط صور ومقاطع فيديو من خلال الكاميرا. ويتيح هذا الإذن للتطبيق استخدام الكاميرا في أي وقت وبدون موافقة منك."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"‏تعطيل مؤشر LED للإرسال عندما تكون الكاميرا قيد الاستخدام"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 1a13170..0985844 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Безопасен режим"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Системно от Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Личен"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Служебен"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Лични приложения"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android за работа"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Услуги, които ви струват пари"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Извършват неща, които могат да ви струват пари."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Вашите съобщения"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Разрешава на приложението да променя глобалните настройки за звука, като например силата и това, кой високоговорител се използва за изход."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"запис на звук"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Разрешава на приложението да записва звук с микрофона. Това разрешение му позволява да го прави по всяко време без потвърждение от ваша страна."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"комуникация със SIM картата"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Разрешава на приложението да изпраща команди до SIM картата. Това е много опасно."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"правене на снимки и видеоклипове"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Разрешава на приложението да прави снимки и видеоклипове с камерата. Това разрешение му позволява да я използва по всяко време без потвърждение от ваша страна."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"деактивиране на светодиодния индикатор за предаване, когато камерата се използва"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 4368908..900aef3 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"+999"</string>
     <string name="safeMode" msgid="2788228061547930246">"Mode segur"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Feina"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Aplicacions personals"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android per a la feina"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Serveis de pagament"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Dur a terme activitats de pagament."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Missatges"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permet que l\'aplicació modifiqui la configuració d\'àudio general, com ara el volum i l\'altaveu de sortida que es fa servir."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"enregistrar àudio"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Permet que l\'aplicació enregistri àudio amb el micròfon. Aquest permís permet que l\'aplicació enregistri àudio en qualsevol moment sense la teva confirmació."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"comunicació SIM"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Permet que l\'aplicació enviï ordres a la SIM. Això és molt perillós."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"fer fotos i vídeos"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Permet que l\'aplicació faci fotos i vídeos amb la càmera. Aquest permís permet que l\'aplicació utilitzi la càmera en qualsevol moment sense la teva confirmació."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"desactiva la transmissió del LED indicador en fer servir la càmera"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index cb8a70a..a004044 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Nouzový režim"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Systém Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Osobní"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Práce"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Osobní aplikace"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android pro práci"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Zpoplatněné služby"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Provádět činnosti, které vás mohou stát peníze."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Vaše zprávy"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Umožňuje aplikaci změnit globální nastavení zvuku, například hlasitost či reproduktor pro výstup zvuku."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"nahrávání zvuku"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Umožňuje aplikaci zaznamenat zvuk pomocí mikrofonu. Toto oprávnění umožňuje aplikaci kdykoliv zaznamenat zvuk bez vašeho svolení."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"komunikace s kartou SIM"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Umožňuje aplikaci odesílat příkazy na kartu SIM. Toto oprávnění je velmi nebezpečné."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"pořizování fotografií a videí"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Umožňuje aplikaci pořizovat fotografie a videa pomocí fotoaparátu. Toto oprávnění umožňuje aplikaci používat fotoaparát kdykoliv i bez vašeho svolení."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"vypnutí indikátoru LED přenosu při použití fotoaparátu"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index a3f71d7..7ad7c81 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Sikker tilstand"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android-system"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Personlig"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Arbejde"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Personlige apps"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android til arbejdsbrug"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Tjenester, der koster dig penge"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Gør ting, der kan koste dig penge."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Dine beskeder"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Tillader, at appen kan ændre globale lydindstillinger, som f.eks. lydstyrke og hvilken højttaler der bruges til output."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"optage lyd"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Tillader, at appen kan optage lyd med mikrofonen. Med denne tilladelse kan appen til enhver tid optage lyd uden din bekræftelse."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM-kommunikation"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Tillader, at appen sender kommandoer til SIM-kortet. Dette er meget farligt."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"tage billeder og optage video"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Tillader, at appen kan tage billeder og videoer med kameraet. Med denne tilladelse kan appen til enhver tid bruge kameraet uden din bekræftelse."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"deaktiver sendelysdioden, når kameraet er i brug"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 293adf3..cc1f042 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Abgesicherter Modus"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android-System"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Privat"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Geschäftlich"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Private Apps"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android für Geschäftliches"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Kostenpflichtige Dienste"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Kostenpflichtige Aktionen"</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Ihre Nachrichten"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Ermöglicht der App, globale Audio-Einstellungen zu ändern, etwa die Lautstärke und den Lautsprecher für die Ausgabe."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"Audio aufnehmen"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Ermöglicht der App, Ton mithilfe des Mikrofons aufzunehmen. Die Berechtigung erlaubt der App, Tonaufnahmen jederzeit und ohne Ihre Bestätigung durchzuführen."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM-Kommunikation"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Ermöglicht der App das Senden von Befehlen an die SIM-Karte. Dies ist äußerst risikoreich."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"Bilder und Videos aufnehmen"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Ermöglicht der App, Bilder und Videos mit der Kamera aufzunehmen. Die Berechtigung erlaubt der App, die Kamera jederzeit und ohne Ihre Bestätigung zu nutzen."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"LED-Anzeige für Übertragung bei Kameranutzung deaktivieren"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 553c27a..a04635d 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Ασφαλής λειτουργία"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Σύστημα Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Προσωπικό"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Εργασία"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Προσωπικές εφαρμογές"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android for Work"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Υπηρεσίες επί πληρωμή"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Πραγματοποίηση ενεργειών για τις οποίες ενδέχεται να χρεωθείτε."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Τα μηνύματά σας"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Επιτρέπει στην εφαρμογή την τροποποίηση καθολικών ρυθμίσεων ήχου, όπως η ένταση και ποιο ηχείο χρησιμοποιείται για έξοδο."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"εγγραφή ήχου"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Επιτρέπει στην εφαρμογή την εγγραφή ήχου με το μικρόφωνο. Αυτή η άδεια δίνει τη δυνατότητα στην εφαρμογή να εγγράφει ήχο ανά πάσα στιγμή χωρίς την έγκρισή σας."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"επικοινωνία με κάρτα sim"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Επιτρέπει στην εφαρμογή την αποστολή εντολών στην κάρτα SIM. Αυτό είναι εξαιρετικά επικίνδυνο."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"λήψη φωτογραφιών και βίντεο"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Επιτρέπει στην εφαρμογή τη λήψη φωτογραφιών και βίντεο με τη φωτογραφική μηχανή. Αυτή η άδεια δίνει τη δυνατότητα στην εφαρμογή να χρησιμοποιεί τη φωτογραφική μηχανή ανά πάσα στιγμή χωρίς την έγκρισή σας."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"απενεργοποίηση ένδειξης LED μετάδοσης όταν χρησιμοποιείται η φωτογραφική μηχανή"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 69cb424..24ae577 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Safe mode"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android System"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Work"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Personal apps"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android for Work"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Services that cost you money"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Do things that can cost you money."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Your messages"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"record audio"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Allows the app to record audio with the microphone. This permission allows the app to record audio at any time without your confirmation."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM communication"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Allows the app to send commands to the SIM. This is very dangerous."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"take pictures and videos"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Allows the app to take pictures and videos with the camera. This permission allows the app to use the camera at any time without your confirmation."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"disable transmit indicator LED when camera is in use"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 69cb424..24ae577 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Safe mode"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android System"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Work"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Personal apps"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android for Work"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Services that cost you money"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Do things that can cost you money."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Your messages"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"record audio"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Allows the app to record audio with the microphone. This permission allows the app to record audio at any time without your confirmation."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM communication"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Allows the app to send commands to the SIM. This is very dangerous."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"take pictures and videos"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Allows the app to take pictures and videos with the camera. This permission allows the app to use the camera at any time without your confirmation."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"disable transmit indicator LED when camera is in use"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 9ffdfe6..3e5538c 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Modo seguro"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Trabajo"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Aplicaciones personales"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android para el trabajo"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Servicios que te cuestan dinero"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Permite que las aplicaciones realicen actividades con cargo."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Tus mensajes"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permite que la aplicación modifique la configuración de audio global, por ejemplo, el volumen y el altavoz de salida."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"grabar audio"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Permite que la aplicación grabe audio con el micrófono. La aplicación puede utilizar este permiso para grabar audio en cualquier momento sin tener tu confirmación."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"Comunicación con tarjeta SIM"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Permite que la aplicación envíe comandos a la tarjeta SIM. Usar este permiso es peligroso."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"tomar fotografías y grabar videos"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Permite que la aplicación saque fotos o grabe videos con la cámara. Este permiso autoriza a la aplicación a utilizar la cámara en cualquier momento sin tu confirmación."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"Inhabilitar el indicador LED de transmisión mientras se utiliza la cámara"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index cf1f4fd..18b321f 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"&gt; 999"</string>
     <string name="safeMode" msgid="2788228061547930246">"Modo seguro"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Trabajo"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Aplicaciones personales"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android para el trabajo"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Servicios por los que tienes que pagar"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Hacer acciones por las que puede que tengas que pagar"</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Tus mensajes"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permite que la aplicación modifique la configuración de audio global (por ejemplo, el volumen y el altavoz de salida)."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"grabar sonido"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Permite que la aplicación grabe audio con el micrófono. La aplicación puede utilizar este permiso para grabar audio en cualquier momento sin tener la confirmación del usuario."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"comunicación con la tarjeta SIM"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Permite que la aplicación envíe comandos a la tarjeta SIM. Este permiso es muy peligroso."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"realizar fotografías y vídeos"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Permite que la aplicación haga fotos o grabe vídeos con la cámara. Este permiso autoriza a la aplicación a utilizar la cámara en cualquier momento sin tu confirmación."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"inhabilitar el indicador LED de transmisión mientras se utiliza la cámara"</string>
diff --git a/core/res/res/values-et-rEE/strings.xml b/core/res/res/values-et-rEE/strings.xml
index 5e0d319..922a9af1 100644
--- a/core/res/res/values-et-rEE/strings.xml
+++ b/core/res/res/values-et-rEE/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Turvarežiim"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android-süsteem"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Isiklik"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Töö"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Isiklikud rakendused"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android töö jaoks"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Tasulised teenused"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Tasuliste toimingute tegemine."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Teie sõnumid"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Võimaldab rakendusel muuta üldiseid heliseadeid, näiteks helitugevust ja seda, millist kõlarit kasutatakse väljundiks."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"salvesta heli"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Võimaldab rakendusel salvestada mikrofoniga heli. See luba võimaldab rakendusel salvestada heli igal ajal ilma teie kinnituseta."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"side SIM-kaardiga"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Lubab rakendusel saata käske SIM-kaardile. See on väga ohtlik."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"piltide ja videote tegemine"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Võimaldab rakendusel teha kaameraga pilte ja videoid. See luba võimaldab rakendusel kasutada kaamerat mis tahes ajal teie kinnituseta."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"keela kaamera kasutamisel näidikutule kasutamine"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 1cb008c..0d3289e 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"بیشتر از 999"</string>
     <string name="safeMode" msgid="2788228061547930246">"حالت ایمن"</string>
     <string name="android_system_label" msgid="6577375335728551336">"‏سیستم Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"شخصی"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"محل کار"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"برنامه‌های شخصی"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"‏Android برای کار"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"سرویس‌های غیر رایگان"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"انجام کارهایی که برای شما هزینه دارد."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"پیام‌های شما"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"به برنامه امکان می‌دهد تنظیمات صوتی کلی مانند میزان صدا و بلندگوی مورد استفاده برای پخش صدا را اصلاح کند."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"ضبط صدا"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"به برنامه اجازه می‌دهد صدا را با میکروفن ضبط کند. این مجوز به برنامه اجازه می‌دهد صدا را در هر زمان که بخواهید بدون تأیید شما ضبط کند."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"ارتباطات سیم کارت"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"به برنامه اجازه ارسال دستورات به سیم کارت را می‌دهد. این بسیار خطرناک است."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"عکسبرداری و فیلمبرداری"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"به برنامه اجازه می‌دهد با دوربین به عکسبرداری و فیلمبرداری بپردازد. این مجوز به برنامه اجازه می‌‌دهد از دوربین در هر زمانی بدون تأیید شما استفاده کند."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"‏LED نشانگر انتقال داده، هنگام استفاده از دوربین غیرفعال شود"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index f792aff..1f4418c 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Suojattu tila"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android-järjestelmä"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Henkilökohtainen"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Työ"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Omat sovellukset"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android työkäyttöön"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Maksulliset palvelut"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Suorita mahdollisesti maksullisia toimintoja."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Omat viestit"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Antaa sovelluksen muokata yleisiä ääniasetuksia, kuten äänenvoimakkuutta ja käytettävää kaiutinta."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"tallentaa ääntä"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Antaa sovelluksen tallentaa ääntä mikrofonin avulla. Sovellus voi tallentaa ääntä milloin tahansa pyytämättä sinulta lupaa."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM-viestintä"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Antaa sovelluksen lähettää komentoja SIM-kortille. Tämä ei ole turvallista."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"ota kuvia ja videoita"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Antaa sovelluksen ottaa kuvia ja kuvata videoita kameralla. Sovellus voi käyttää kameraa milloin tahansa ilman lupaasi."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"poista lähetyksen merkkivalo käytöstä, kun kameraa käytetään"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index c43fe1e..3b30f65 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"&gt;999"</string>
     <string name="safeMode" msgid="2788228061547930246">"Mode sécurisé"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Système Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Personnel"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Travail"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Applications personnelles"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android pour les activités professionnelles"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Services payants"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Effectuer des opérations payantes"</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Vos messages"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permet à l\'application de modifier les paramètres audio généraux, tels que le volume et la sortie audio utilisée."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"enregistrer fichier audio"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Permet à l\'application d\'enregistrer des contenus audio à l\'aide du microphone. Cette autorisation lui donne la possibilité d\'enregistrer du contenu audio à tout moment sans votre consentement."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"Communication avec la carte SIM"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Permet à l\'application d\'envoyer des commandes à la carte SIM. Cette fonctionnalité est très dangereuse."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"prendre des photos et filmer des vidéos"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Permet à l\'application de prendre des photos et de filmer des vidéos avec l\'appareil photo. Cette autorisation lui permet d\'utiliser l\'appareil photo à tout moment sans votre consentement."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"désactiver l\'indicateur d\'émission LED lorsque la caméra est en cours d\'utilisation"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index d755f05..abc8f9a 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"&gt;999"</string>
     <string name="safeMode" msgid="2788228061547930246">"Mode sécurisé"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Système Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Personnel"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Professionnel"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Applications personnelles"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android pour les activités professionnelles"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Services payants"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Effectuer des opérations payantes"</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Vos messages"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permet à l\'application de modifier les paramètres audio généraux, tels que le volume et la sortie audio utilisée."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"enregistrer des fichiers audio"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Permet à l\'application d\'enregistrer des contenus audio à l\'aide du microphone. Cette autorisation lui donne la possibilité d\'enregistrer du contenu audio à tout moment sans votre consentement."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"Communication avec la carte SIM"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Autoriser l\'envoi de commandes à la carte SIM via l\'application. Cette fonctionnalité est très risquée."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"prendre des photos et enregistrer des vidéos"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Permet à l\'application de prendre des photos et de filmer des vidéos avec l\'appareil photo. Cette autorisation lui permet d\'utiliser l\'appareil photo à tout moment sans votre consentement."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"désactiver l\'indicateur d\'émission LED lorsque la caméra est en cours d\'utilisation"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index ff4412ea..ef2205b 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"सुरक्षित मोड"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android सिस्‍टम"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"व्यक्तिगत"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"कार्यालय"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"व्यक्तिगत ऐप्स"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"कार्यालय के लिए Android"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"वे सेवाएं जिन पर आप खर्चा करते हैं"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"ऐसे कार्य करें जिससे आपका धन खर्च हो सकता है."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"आपके संदेश"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"ऐप्स  को वैश्विक ऑडियो सेटिंग, जैसे वॉल्‍यूम और कौन-सा स्पीकर आउटपुट के लिए उपयोग किया गया, संशोधित करने देता है."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"ऑडियो रिकॉर्ड करें"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"ऐप्स  को माइक्रोफ़ोन द्वारा ऑडियो रिकार्ड करने देता है. यह अनुमति ऐप्स  को आपकी पुष्टि के बिना किसी भी समय ऑडियो रिकार्ड करने देती है."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"सिम संचार"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"ऐप्स को सिम में आदेश भेजने देती है. यह बहुत ही खतरनाक है."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"चित्र और वीडियो लें"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"ऐप्स  को कैमरे से चित्र और वीडियो लेने देता है. यह अनुमति ऐप्स  को किसी भी समय आपकी पुष्टि के बिना कैमरे का उपयोग करने देती है."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"कैमरा उपयोग में होने पर संचारण संकेतक LED अक्षम करें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 4794fac..6badaac 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Siguran način rada"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Sustav Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Osobno"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Posao"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Osobne aplikacije"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android za rad"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Usluge koje se plaćaju"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Radite stvari koje će uzrokovati novčane troškove."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Vaše poruke"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Aplikaciji omogućuje izmjenu globalnih postavki zvuka, primjerice glasnoće i zvučnika koji se upotrebljava za izlaz."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"snimanje zvuka"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Aplikaciji omogućuje snimanje zvuka mikrofonom. Ta dozvola aplikaciji omogućuje snimanje zvuka u bilo kojem trenutku bez vašeg odobrenja."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"komunikacija sa SIM-om"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Omogućuje aplikaciji slanje naredbi SIM-u. To je vrlo opasno."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"snimi fotografije i videozapise"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Aplikaciji omogućuje snimanje slika i videozapisa fotoaparatom. Ta dozvola aplikaciji omogućuje upotrebu fotoaparata u bilo kojem trenutku bez vašeg odobrenja."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"onemogućavanje lampice pokazivača prijenosa kada je fotoaparat u upotrebi"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 136075b..6d0ff1b 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Biztonsági üzemmód"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android rendszer"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Személyes"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Munkahelyi"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Személyes alkalmazások"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android munkához"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Fizetős szolgáltatások"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Olyan dolgok végrehajtása, amelyek pénzbe kerülnek."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Saját üzenetek"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Lehetővé teszi az alkalmazás számára az általános hangbeállítások, például a hangerő és a használni kívánt kimeneti hangszóró módosítását."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"hanganyag rögzítése"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Lehetővé teszi az alkalmazás számára a mikrofonnal való hangfelvételt.Az engedéllyel rendelkező alkalmazás az Ön jóváhagyása nélkül, bármikor rögzíthet hanganyagot."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM-kommunikáció"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Engedélyezi, hogy az alkalmazás parancsokat küldjön a SIM kártyára. Ez rendkívül veszélyes lehet."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"fotók és videók készítése"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Lehetővé teszi az alkalmazás számára, hogy a fényképezőgéppel fotókat és videókat készítsen. Az engedéllyel rendelkező alkalmazás bármikor, az Ön jóváhagyása nélkül használhatja a fényképezőgépet."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"átviteljelző LED letiltása, ha a kamera használatban van"</string>
diff --git a/core/res/res/values-hy-rAM/strings.xml b/core/res/res/values-hy-rAM/strings.xml
index d7adca7..5591bfb 100644
--- a/core/res/res/values-hy-rAM/strings.xml
+++ b/core/res/res/values-hy-rAM/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Անվտանգ ռեժիմ"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android համակարգ"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Անձնական"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Աշխատանքային"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Անձնական ​​ծրագրեր"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android for Work"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Ծառայություններ, որոնց համար կգանձվեք"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Կատարել գործողություններ, որի դիմաց ձեր հաշվից գումար կծախսվի:"</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Ձեր հաղորդագրությունները"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Թույլ է տալիս հավելվածին փոփոխել ձայնանյութի գլոբալ կարգավորումները, ինչպես օրինակ` ձայնը և թե որ խոսափողն է օգտագործված արտածման համար:"</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"ձայնագրել ձայնանյութ"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Թույլ է տալիս հավելվածին բարձրախոսով ձայնագրել ձայնանյութ: Այս թույլտվությունը հնարավորություն է տալիս հավելվածին ձայնանյութ ձայնագրել ցանկացած ժամանակ` առանց ձեր հաստատման:"</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM հաղորդակցում"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Թույլ է տալիս հավելվածին հրամաններ ուղարկել SIM-ին: Սա շատ վտանգավոր է:"</string>
     <string name="permlab_camera" msgid="3616391919559751192">"լուսանկարել և տեսանկարել"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Թույլ է տալիս հավելվածին ֆոտոխցիկով լուսանկարել և տեսանկարել: Այս թույլտվությունը հնարավորություն է տալիս հավելվածին օգտագործել ֆոտոխցիկը ցանկացած ժամանակ` առանց ձեր հաստատման:"</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"անջատել փոխանցող LED ցուցիչը, երբ ֆոտոխցիկը օգտագործվում է"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 8ace2df..52da0cb 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Mode aman"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Sistem Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Pribadi"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Kantor"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Aplikasi pribadi"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android untuk Bekerja"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Layanan berbayar"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Lakukan hal yang dapat dikenai biaya."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Pesan Anda"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Memungkinkan aplikasi mengubah setelan audio global, misalnya volume dan pengeras suara mana yang digunakan untuk keluaran."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"rekam audio"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Memungkinkan aplikasi merekam audio dengan mikrofon. Izin ini memungkinkan aplikasi merekam audio kapan saja tanpa konfirmasi Anda."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"komunikasi sim"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Mengizinkan aplikasi mengirim perintah ke SIM. Ini sangat berbahaya."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"ambil gambar dan video"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Memungkinkan aplikasi mengambil gambar dan video dengan kamera. Izin ini memungkinkan aplikasi menggunakan kamera kapan saja tanpa konfirmasi Anda."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"nonaktifkan LED indikator transmisi saat kamera digunakan"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 56e309b..9ef396e 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Modalità provvisoria"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Personale"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Lavoro"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"App personali"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android for Work"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Servizi che prevedono un costo"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Svolgono operazioni che possono comportare un costo."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"I tuoi messaggi"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Consente all\'applicazione di modificare le impostazioni audio globali, come il volume e quale altoparlante viene utilizzato per l\'uscita."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"registrazione audio"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Consente all\'applicazione di registrare audio con il microfono. Questa autorizzazione consente all\'applicazione di registrare audio in qualsiasi momento senza la tua conferma."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"comunicazione SIM"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Consente all\'app di inviare comandi alla SIM. Questo è molto pericoloso."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"acquisizione di foto e video"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Consente all\'applicazione di scattare foto e riprendere video con la fotocamera. Questa autorizzazione consente all\'applicazione di utilizzare la fotocamera in qualsiasi momento senza la tua conferma."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"disabilitazione del LED di indicazione della trasmissione quando la fotocamera è in uso"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 6f06252..c7cbe56 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"מצב בטוח"</string>
     <string name="android_system_label" msgid="6577375335728551336">"‏מערכת Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"אישי"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"עבודה"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"אפליקציות אישיות"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"‏Android לעבודה"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"שירותים שעולים כסף"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"ביצוע פעולות שעשויות לעלות לך כסף."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"ההודעות שלך"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"מאפשר לאפליקציה לשנות הגדרות אודיו גלובליות כמו עוצמת קול ובחירת הרמקול המשמש לפלט."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"הקלט אודיו"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"מאפשר לאפליקציה להקליט אודיו באמצעות המיקרופון. אישור זה מתיר לאפליקציה להקליט אודיו בכל עת ללא אישורך."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"‏תקשורת SIM"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"‏מאפשרת ליישום לשלוח פקודות ל-SIM. זוהי הרשאה מסוכנת מאוד."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"צלם תמונות וסרטונים"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"מאפשר לאפליקציה לצלם תמונות וסרטונים באמצעות המצלמה. אישור זה מאפשר לאפליקציה להשתמש במצלמה בכל עת ללא אישורך."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"השבת את נורית מצב השידור כשהמצלמה בשימוש"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 8d22465..281aaa9 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"セーフモード"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Androidシステム"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"プライベート"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"職場"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"プライベートアプリ"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"ビジネス向けAndroid"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"料金の発生するサービス"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"料金発生の可能性がある操作を実行します。"</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"送受信したメッセージ"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"音声全般の設定(音量、出力に使用するスピーカーなど)の変更をアプリに許可します。"</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"録音"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"マイクを使った録音をアプリに許可します。これにより、アプリがいつでも確認なしで録音できるようになります。"</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM通信"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"SIMにコマンドを送信することをアプリに許可します。この許可は非常に危険です。"</string>
     <string name="permlab_camera" msgid="3616391919559751192">"写真と動画の撮影"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"カメラでの写真と動画の撮影をアプリに許可します。これにより、アプリが確認なしでいつでもカメラを使用できるようになります。"</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"カメラの使用中に通信インジケータLEDを無効にする"</string>
diff --git a/core/res/res/values-ka-rGE/strings.xml b/core/res/res/values-ka-rGE/strings.xml
index be17c23..01e68a2 100644
--- a/core/res/res/values-ka-rGE/strings.xml
+++ b/core/res/res/values-ka-rGE/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"უსაფრთხო რეჟიმი"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android-ის სისტემა"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"პირადი"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"სამსახური"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"პერსონალური აპები"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android სამსახურისთვის"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"სერვისები, რომელშიც ფულის გადახდა გიწევთ"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"ისეთი აქტივობების განხორციელება, რომლებშიც ფულის გადახდა მოგიწევთ."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"თქვენი შეტყობინებები"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"აპს შეეძლება აუდიოს გლობალური პარამეტრების შეცვლა. მაგ.: ხმის სიმაღლე და რომელი დინამიკი გამოიყენება სიგნალის გამოსტანად."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"აუდიოს ჩაწერა"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"აპს შეეძლება აუდიო ჩაწერა მიკროფონით. ნებართვა აპს აუდიო ჩაწერის უფლებას აძლევს ნებისმიერ დროს, თქვენი თანხმობის გარეშე."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"კომუნიკაცია SIM-თან"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"აპისთვის ნების დართვა გაუგზავნოს ბრძანებები SIM-ბარათს. ეს ძალიან საშიშია."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"სურათებისა და ვიდეოების გადაღება"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"აპს შეეძლება კამერით სურათისა და ვიდეოს გადაღება. ეს ნებართვა აპს უფლებას აძლევს, ნებისმიერ დროს გამოიყენოს კამერა თქვენი დადასტურების გარეშე."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"კამერის გამოყენებისას გადამცემი ინდიკატორის LED გათიშვა"</string>
diff --git a/core/res/res/values-km-rKH/strings.xml b/core/res/res/values-km-rKH/strings.xml
index d5d1715f8..ec389b1 100644
--- a/core/res/res/values-km-rKH/strings.xml
+++ b/core/res/res/values-km-rKH/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"របៀប​​​សុវត្ថិភាព"</string>
     <string name="android_system_label" msgid="6577375335728551336">"ប្រព័ន្ធ​​ Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"ផ្ទាល់ខ្លួន"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"កន្លែង​ធ្វើ​ការ"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"កម្មវិធី​ផ្ទាល់​ខ្លួន"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android សម្រាប់​ការងារ"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"សេវាកម្ម​ដែល​កាត់​លុយ​របស់​អ្នក"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"ធ្វើ​អ្វី​ដែល​អាច​កាត់​លុយ​របស់​អ្នក។"</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"សារ​របស់​អ្នក"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"ឲ្យ​កម្មវិធី​កែ​ការ​កំណត់​សំឡេង​សកល ដូច​ជា​កម្រិត​សំឡេង និង​អូប៉ាល័រ​ដែល​បាន​ប្រើ​សម្រាប់​លទ្ធផល។"</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"ថត​សំឡេង"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"​ឱ្យ​កម្មវិធី​ថត​សំឡេង​​ជាមួយ​មីក្រូហ្វូន​​​។ សិទ្ធិ​នេះ​អនុញ្ញាត​ឲ្យ​កម្មវិធី​ថត​សំឡេង​​នៅ​ពេល​ណា​មួយ​ដោយ​គ្មាន​ការ​បញ្ជាក់​របស់​អ្នក។"</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"ការ​ភ្ជាប់​ស៊ីមកាត"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"ឲ្យ​កម្មវិធី​ផ្ញើ​ពាក្យ​បញ្ជា​ទៅ​ស៊ីម​កាត។ វា​គ្រោះ​ថ្នាក់​ណាស់។"</string>
     <string name="permlab_camera" msgid="3616391919559751192">"ថត​រូប និងវីដេអូ"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"ឲ្យ​កម្មវិធី​ថត​រូប និង​វីដេអូ​ដោយ​ប្រើ​ម៉ាស៊ីន​ថត។ វា​ឲ្យ​កម្មវិធី​​ប្រើ​ម៉ាស៊ីន​ថត​នៅ​ពេល​​ណាមួយ​ដោយ​គ្មាន​ការ​បញ្ជាក់​របស់​អ្នក។"</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"បិទ​​ពន្លឺ​បង្ហាញ​ការ​បញ្ជូន​​ពេល​ម៉ាស៊ីន​ថត​កំពុង​ប្រើ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 77e83dd..2d8d0cd 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"안전 모드"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android 시스템"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"개인"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"직장"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"개인 앱"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"업무용 Android"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"요금이 부과되는 서비스"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"요금이 부과될 수 있는 작업을 수행할 수 있도록 합니다."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"메시지"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"앱이 음량이나 출력을 위해 사용하는 스피커 등 전체 오디오 설정을 변경할 수 있도록 허용합니다."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"오디오 녹음"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"앱이 마이크로 오디오를 녹음할 수 있도록 허용합니다. 이 권한을 사용하면 앱이 사용자의 확인 없이 언제든지 오디오를 녹음할 수 있습니다."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM 통신"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"앱이 SIM에 명령어를 전송할 수 있도록 허용합니다. 이 기능은 매우 위험합니다."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"사진과 동영상 찍기"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"앱이 카메라로 사진과 동영상을 찍을 수 있도록 허용합니다. 이 권한을 사용하면 앱이 언제든지 사용자의 확인 없이 카메라를 사용할 수 있습니다."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"카메라를 사용할 때 전송 표시 LED 사용 중지"</string>
diff --git a/core/res/res/values-lo-rLA/strings.xml b/core/res/res/values-lo-rLA/strings.xml
index 17a4236..f7ae955 100644
--- a/core/res/res/values-lo-rLA/strings.xml
+++ b/core/res/res/values-lo-rLA/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Safe mode"</string>
     <string name="android_system_label" msgid="6577375335728551336">"ລະບົບ Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"​ສ່ວນ​ໂຕ"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"​ບ່ອນ​ເຮັດ​ວຽກ"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"​ແອັບຯ​ສ່ວນ​ໂຕ"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android ​ສຳ​ລັບ​ບ່ອນ​ເຮັດ​ວຽກ"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"ບໍລິການທີ່ເຮັດໃຫ້ທ່ານເສຍເງິນ"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"ເຮັດສິ່ງທີ່ທ່ານຕ້ອງເສຍຄ່າໃຊ້ຈ່າຍ."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"ຂໍ້ຄວາມຂອງທ່ານ"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"ອະນຸຍາດໃຫ້ແອັບຯແກ້ໄຂການຕັ້ງຄ່າສຽງສ່ວນກາງ ເຊັ່ນ: ລະດັບສຽງ ແລະລຳໂພງໃດທີ່ຖືກໃຊ້ສົ່ງສຽງອອກ."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"ບັນທຶກສຽງ"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"ອະນຸຍາດໃຫ້ແອັບຯບັນທຶກສຽງດ້ວຍໄມໂຄຣໂຟນໄດ້. ການອະນຸຍາດນີ້ຈະເຮັດໃຫ້ແອັບຯ ສາມາດບັນທຶກສຽງໄດ້ຕະຫລອດເວລາ ໂດຍບໍ່ຕ້ອງຖ້າການຢືນຢັນຈາກທ່ານ."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"ການສື່ສານຂອງ SIM"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"ອະນຸຍາດໃຫ້ແອັບຯສົ່ງຄຳສັ່ງຫາ SIM. ສິ່ງນີ້ອັນຕະລາຍຫຼາຍ."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"ຖ່າຍຮູບ ແລະວິດີໂອ"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"ອະນຸຍາດໃຫ້ແອັບຯຖ່າຍຮູບ ແລະວິດີໂອດ້ວຍກ້ອງຖ່າຍຮູບ. ການອະນຸຍາດນີ້ຈະອານຸຍາດໃຫ້ແອັບຯ ສາມາດໃຊ້ກ້ອງຖ່າຍຮູບໄດ້ຕະຫລອດເວລາ ໂດຍບໍ່ຕ້ອງຖ້າການຢືນຢັນຈາກທ່ານ."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"ປິດໄຟສັນຍານ LED ເມື່ອນຳໃຊ້ກ້ອງ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 2cd171e..5865662 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Saugos režimas"</string>
     <string name="android_system_label" msgid="6577375335728551336">"„Android“ sistema"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Asmeninė"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Darbo"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Asmeninės programos"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Darbui skirta „Android“"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Paslaugos, už kurias mokėjote"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Atlikite mokamus veiksmus."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Jūsų pranešimai"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Leidžiama programai keisti visuotinius garso nustatymus, pvz., garsumą ir tai, kuris garsiakalbis naudojamas išvesčiai."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"įrašyti garsą"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Leidžiama programai įrašyti garsą naudojant mikrofoną. Šis leidimas suteikia galimybę programai įrašyti garsą bet kada be jūsų patvirtinimo."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM kortelės ryšys"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Programai leidžiama siųsti komandas į SIM kortelę. Tai labai pavojinga."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"fotografuoti ir filmuoti"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Leidžiama programai fotografuoti ir filmuoti kamera. Šis leidimas suteikia teisę programai naudoti kamerą bet kada be jūsų patvirtinimo."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"neleisti perduoti LED indikatoriaus, kai naudojamas fotoaparatas"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index ae037fd..8b69875 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"Pārsniedz"</string>
     <string name="safeMode" msgid="2788228061547930246">"Drošais režīms"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android sistēma"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Personisks"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Darba"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Personīgās lietotnes"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android darbam"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Maksas pakalpojumi"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Veikt darbības, par kurām, iespējams, būs jāmaksā."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Jūsu ziņojumi"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Ļauj lietotnei mainīt globālos audio iestatījumus, piemēram, skaļumu un izejai izmantoto skaļruni."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"ierakstīt audio"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Ļauj lietotnei ierakstīt audio, izmantojot mikrofonu. Šī atļauja ļauj lietotnei ierakstīt audio jebkurā brīdī bez jūsu apstiprinājuma."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM saziņa"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Ļauj lietotnei sūtīt komandas uz SIM karti. Tas ir ļoti bīstami!"</string>
     <string name="permlab_camera" msgid="3616391919559751192">"uzņemt attēlus un videoklipus"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Ļauj lietotnei uzņemt attēlus un videoklipus ar kameru. Ar šo atļauju lietotne var jebkurā brīdī izmantot kameru bez jūsu apstiprinājuma."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"Atspējot pārraidīšanas LED indikatoru, kad kamera tiek izmantota"</string>
diff --git a/core/res/res/values-mn-rMN/strings.xml b/core/res/res/values-mn-rMN/strings.xml
index c515b87..9f4028a 100644
--- a/core/res/res/values-mn-rMN/strings.xml
+++ b/core/res/res/values-mn-rMN/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Аюулгүй горим"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Андройд систем"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Хувийн"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Ажил"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Хувийн апп-ууд"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Ажилд зориулсан Андройд"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Танаас төлбөр авдаг үйлчилгээнүүд"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Таны төлбөрт оруулах зүйлийг хийх."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Таны мессеж"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Апп нь дууны хэмжээ, спикерын гаралтад ашиглагдах глобал аудио тохиргоог өөрчлөх боломжтой."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"аудио бичих"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Апп нь микрофоноор аудио бичих боломжтой. Энэ зөвшөөрөл нь апп-д ямар ч үед таны зөвшөөрөлгүйгээр аудио бичих боломжийг олгоно."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"сим холбоо"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Апп-д SIM рүү комманд илгээхийг зөвшөөрнө. Энэ маш аюултай."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"зураг авах болон видео бичих"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Апп нь камераар зураг авах болон видео бичих боломжтой. Энэ зөвшөөрөл нь апп-д ямар ч үед таны зөвшөөрөлгүйгээр камер ашиглах боломжийг олгоно."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"камер ашиглаж байх үед дамжууллыг заагч LED-г идэвхгүй болгох"</string>
diff --git a/core/res/res/values-ms-rMY/strings.xml b/core/res/res/values-ms-rMY/strings.xml
index 637b542..91c7c95 100644
--- a/core/res/res/values-ms-rMY/strings.xml
+++ b/core/res/res/values-ms-rMY/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Mod selamat"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Sistem Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Peribadi"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Tempat Kerja"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Apl peribadi"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android untuk Kerja"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Perkhidmatan yang anda perlu bayar"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Melakukan perkara yang boleh mengenakan bayaran kepada anda."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Mesej anda"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Membenarkan apl untuk mengubah suai tetapan audio global seperti kelantangan dan pembesar suara mana digunakan untuk output."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"rakam audio"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Membenarkan apl untuk merakam audio menggunakan mikrofon. Kebenaran ini membenarkan apl untuk merakam audio pada bila-bila masa tanpa pengesahan anda."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"komunikasi sim"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Membenarkan apl menghantar arahan kepada SIM. Ini amat berbahaya."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"ambil gambar dan video"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Membenarkan apl mengambil gambar dan video menggunakan kamera. Kebenaran ini membenarkan apl untuk menggunakan kamera pada bila-bila masa tanpa pengesahan anda."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"lumpuhkan LED penunjuk penghantaran semasa kamera sedang digunakan"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 62db1da..dcc648e 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Sikkermodus"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android-system"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Personlig"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Jobb"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Personlige apper"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android for arbeidsplassen"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Betaltjenester"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Gjøre ting som kan koste deg penger."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Meldinger"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Lar appen endre globale lydinnstillinger slik som volum og hvilken høyttaler som brukes for lydavspilling."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"ta opp lyd"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Lar appen ta opp lyd med mikrofonen. Dette betyr at appen kan ta opp lyd når som helst uten at du har bedt om det."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"sim-kommunikasjon"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Lar appen sende kommandoer til SIM-kortet. Dette er veldig farlig."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"ta bilder og videoer"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Lar appen ta bilder og filme med kameraet. Denne tillatelsen gjør at appen kan bruke kameraet når som helst uten bekreftelse fra deg."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"deaktiver LED-lyset for indikering av overføring når kameraet er i bruk"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 1cc6918..4815123 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999 +"</string>
     <string name="safeMode" msgid="2788228061547930246">"Veilige modus"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android-systeem"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Persoonlijk"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Werk"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Persoonlijke apps"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android voor werk"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Services waarvoor u moet betalen"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Activiteiten uitvoeren waarvoor kosten in rekening kunnen worden gebracht."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Uw berichten"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Hiermee kan de app algemene audio-instellingen wijzigen zoals het volume en welke luidspreker wordt gebruikt voor de uitvoer."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"audio opnemen"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Hiermee kan de app audio opnemen met de microfoon. Met deze toestemming kan de app op elk moment audio opnemen, zonder om uw bevestiging te vragen."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"sim-communicatie"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Hiermee kan de app opdrachten verzenden naar de simkaart. Dit is erg gevaarlijk."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"foto\'s en video\'s maken"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Hiermee kan de app foto\'s en video\'s maken met de camera. Met deze toestemming kan de app de camera altijd gebruiken, zonder uw bevestiging."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"indicatielampje uitschakelen wanneer camera wordt gebruikt"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 5a75c342..cfbb622 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"&gt;999"</string>
     <string name="safeMode" msgid="2788228061547930246">"Tryb awaryjny"</string>
     <string name="android_system_label" msgid="6577375335728551336">"System Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Osobiste"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Praca"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Aplikacje osobiste"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android w pracy"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Usługi płatne"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Wykonywanie czynności, za które pobierana jest opłata."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Twoje wiadomości"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Pozwala aplikacji na modyfikowanie globalnych ustawień dźwięku, takich jak głośność oraz urządzenie wyjściowe."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"nagrywanie dźwięku"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Pozwala aplikacji na nagrywanie dźwięku przez mikrofon. Aplikacja z tym uprawnieniem może nagrywać dźwięk w dowolnym momencie bez Twojego potwierdzenia."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"komunikacja z kartą SIM"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Pozwala aplikacji na wysyłanie poleceń do karty SIM. To bardzo niebezpieczne."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"wykonywanie zdjęć i filmów wideo"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Pozwala aplikacji na robienie zdjęć i nagrywanie filmów przy użyciu aparatu. Aplikacja z tym uprawnieniem może użyć aparatu w dowolnym momencie bez Twojego potwierdzenia."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"wyłącz wskaźnik LED transmisji, gdy aparat jest w użyciu"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 85a7a7e..0ddc2d3 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Modo seguro"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Pessoal"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Trabalho"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Aplicações pessoais"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android para trabalho"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Serviços que implicam pagamento"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Efetuar ações que implicam pagamento."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"As suas mensagens"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permite que a aplicação modifique definições de áudio globais, tais como o volume e qual o altifalante utilizado para a saída de som."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"gravar áudio"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Permite que a aplicação grave áudio com o microfone. Esta autorização permite que a aplicação grave áudio em qualquer altura sem a confirmação do utilizador."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"comunicação com o SIM"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Permite que a aplicação envie comandos para o SIM. Esta ação é muito perigosa."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"tirar fotografias e vídeos"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Permite que a aplicação tire fotografias e grave vídeos com a câmara. Esta autorização permite que a aplicação utilize a câmara sem a sua confirmação em qualquer altura."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"desativar LED indicador de transmissão com a câmara em utilização"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index cd30e91..1e36eaf 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"&gt;999"</string>
     <string name="safeMode" msgid="2788228061547930246">"Modo de segurança"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Pessoal"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Trabalho"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Aplicativos pessoais"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android para o trabalho"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Serviços que geram gastos"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Faça coisas que podem custar dinheiro."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Suas mensagens"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permite que o aplicativo modifique configurações de áudio globais como volume e alto-falantes de saída."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"gravar áudio"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Permite que o aplicativo grave áudio com o microfone. Esta permissão autoriza o aplicativo a gravar áudio a qualquer momento, sem sua confirmação."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"comunicação com sim"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Permite que o aplicativo envie comandos ao SIM. Muito perigoso."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"tirar fotos e gravar vídeos"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Permite que o aplicativo tire fotos e filme vídeos com a câmera. Esta permissão autoriza o aplicativo a usar a câmera a qualquer momento sem sua confirmação."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"desativar a transmissão do LED indicador quando a câmera estiver em uso"</string>
diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml
index 994b8f2..9ba2fbe 100644
--- a/core/res/res/values-rm/strings.xml
+++ b/core/res/res/values-rm/strings.xml
@@ -246,9 +246,9 @@
     <skip />
     <string name="safeMode" msgid="2788228061547930246">"Modus segirà"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Sistem Android"</string>
-    <!-- no translation found for user_owner_label (2804351898001038951) -->
+    <!-- no translation found for user_owner_label (6465364741001216388) -->
     <skip />
-    <!-- no translation found for managed_profile_label (6260850669674791528) -->
+    <!-- no translation found for managed_profile_label (3022906847647343112) -->
     <skip />
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Servetschs che custan"</string>
     <!-- no translation found for permgroupdesc_costMoney (3293301903409869495) -->
@@ -885,6 +885,10 @@
     <string name="permlab_recordAudio" msgid="3876049771427466323">"registrar audio"</string>
     <!-- no translation found for permdesc_recordAudio (4906839301087980680) -->
     <skip />
+    <!-- no translation found for permlab_sim_communication (1180265879464893029) -->
+    <skip />
+    <!-- no translation found for permdesc_sim_communication (5725159654279639498) -->
+    <skip />
     <string name="permlab_camera" msgid="3616391919559751192">"fotografar e registrar videos"</string>
     <!-- no translation found for permdesc_camera (8497216524735535009) -->
     <skip />
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index eb56be3..8ab8ccf 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"˃999"</string>
     <string name="safeMode" msgid="2788228061547930246">"Mod sigur"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Sistemul Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Serviciu"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Aplicații personale"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android pentru serviciu"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Servicii cu plată"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Efectuează acţiuni care sunt cu plată."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Mesajele dvs."</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permite aplicaţiei să modifice setările audio globale, cum ar fi volumul şi difuzorul care este utilizat pentru ieşire."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"înregistrare audio"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Permite aplicaţiei să efectueze înregistrări audio cu ajutorul microfonului. Cu această permisiune aplicaţia efectuează oricând înregistrări audio fără confirmare."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"comunicare cu cardul SIM"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Permite aplicației să trimită comenzi pe cardul SIM. Această permisiune este foarte periculoasă."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"realizarea de fotografii şi videoclipuri"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Permite aplicaţiei să realizeze fotografii şi videoclipuri cu camera foto. Cu această permisiune aplicaţia utilizează camera foto oricând şi fără confirmare."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"dezactivează ledul care indică când este utilizată camera foto"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 52dce95..17a0396 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"&gt;999"</string>
     <string name="safeMode" msgid="2788228061547930246">"Безопасный режим"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Система Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Личные данные"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Работа"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Персональные приложения"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android для бизнеса"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Платные услуги"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Приложение сможет использовать платные услуги."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Сообщения"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Приложение сможет изменять системные настройки звука, например уровень громкости и активный динамик."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"Запись аудио"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Приложение сможет записывать аудио с помощью микрофона в любое время без уведомления."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"Обращение к SIM-карте"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Приложение сможет отправлять команды SIM-карте (данное разрешение представляет большую угрозу)."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"Фото- и видеосъемка"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Приложение сможет снимать фотографии и видеоролики с помощью камеры в любое время без вашего разрешения."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"Отключать светодиодный индикатор во время использования камеры"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 2ee9310..e98a4be 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Núdzový režim"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Systém Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Osobné"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Práca"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Osobné aplikácie"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android na prácu"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Spoplatnené služby"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Vykonávanie činností, ktoré vás môžu stáť peniaze."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Vaše správy"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Umožňuje aplikácii upraviť globálne nastavenia zvuku, ako je hlasitosť, alebo určiť, z ktorého reproduktora bude zvuk vychádzať."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"nahrávať zvuk"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Umožňuje aplikácii zaznamenávať zvuk pomocou mikrofónu. Toto povolenie umožňuje aplikácii zaznamenávať zvuk kedykoľvek bez vášho potvrdenia."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"komunikácia s kartou SIM"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Umožňuje aplikácii odosielať príkazy na kartu SIM. Toto je veľmi nebezpečné povolenie."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"fotiť a nakrúcať videá"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Umožňuje aplikácii fotografovať a nahrávať videá pomocou fotoaparátu. Toto povolenie umožňuje aplikácii používať fotoaparát kedykoľvek a bez vášho potvrdenia."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"Zakázať indikátor LED prenosu pri používaní fotoaparátu"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 8068603..280e499 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999 +"</string>
     <string name="safeMode" msgid="2788228061547930246">"Varni način"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Sistem Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Osebno"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Služba"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Osebne aplikacije"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android za delo"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Plačljive storitve"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Dovolite stvari, za katere bo morda treba plačati."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Vaša sporočila"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Aplikaciji omogoča spreminjanje splošnih zvočnih nastavitev, na primer glasnost in kateri zvočnik se uporablja."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"snemanje zvoka"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Aplikaciji omogoča snemanje zvoka z mikrofonom. S tem dovoljenjem lahko aplikacija kadar koli snema zvok brez vaše potrditve."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"komuniciranje s kartico SIM"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Aplikaciji dovoli pošiljanje ukazov kartici SIM. To je lahko zelo nevarno."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"fotografiranje in snemanje videoposnetkov"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Aplikaciji omogoča fotografiranje in snemanje videoposnetkov s kamero. S tem dovoljenjem lahko aplikacija kadar koli uporablja kamero brez vaše potrditve."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"onemogoči LED-indikator prenašanja, ko je fotoaparat v uporabi"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 05d71f2..6147fd8 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Безбедни режим"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android систем"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Лично"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Посао"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Личне апликације"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android за посао"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Услуге које се плаћају"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Покреће радње које могу да се плаћају."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Поруке"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Дозвољава апликацији да мења глобална аудио подешавања као што су јачина звука и избор звучника који се користи као излаз."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"снимање аудио записа"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Дозвољава апликацији да снима звук помоћу микрофона. Ова дозвола омогућава апликацији да снима звук у било ком тренутку без ваше потврде."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"Комуникација са SIM картицом"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Омогућава апликацији да шаље команде SIM картици. То је веома опасно."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"снимање фотографија и видео снимака"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Дозвољава апликацији да снима слике и видео снимке камером. Ова дозвола омогућава апликацији да у било ком тренутку користи камеру без ваше потврде."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"онемогући пренос LED осветљења индикатора док се камера користи"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 1c3f560..acd5bee 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Säkert läge"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android-system"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Personligt"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Arbetet"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Personliga appar"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android för arbetet"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Tjänster som kostar pengar"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Göra saker som kan kosta pengar."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Dina meddelanden"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Tillåter att appen ändrar globala ljudinställningar som volym och vilken högtalarutgång som används."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"spela in ljud"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Tillåter att appen spelar in ljud med mikrofonen. Med den här behörigheten tillåts appen att spela in ljud när som helst utan ditt godkännande."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM-kommunikation"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Tillåter att appen skickar kommandon till SIM-kortet. Detta är mycket farligt."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"ta bilder och spela in videoklipp"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Tillåter att appen tar bilder och spelar in videor med kameran. Med den här behörigheten tillåts appen att använda kameran när som helst utan ditt godkännande."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"inaktivera LED-sändningsindikator när kameran används"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 83fe801..b177516 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Mtindo salama"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Mfumo wa Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Binafsi"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Kazini"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Programu binafsi"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android kwa Kazi"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Huduma ambazo zinakugharimu pesa"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Fanya mambo ambayo yanaweza kukugharimu pesa."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Ujumbe wako"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Inaruhusu programu kurekebisha mipangilio ya sauti kila mahali kama vile sauti na ni kipaza sauti kipi ambacho kinatumika kwa kutoa."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"kurekodi sauti"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Inaruhusu programu kurekodi sauti kwa kinasa sauti. Idhini hii inaruhusu programu kurekodi sauti wakati wowote bila ya uthibitisho wako."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"mawasiliano ya sim"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Huruhusu programu kutuma amri kwa SIM. Hii ni hatari sana."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"Kupiga picha na kurekodi video"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Inaruhusu programu kupiga picha na video kwa kamera. Kibali hiki kinaruhusu programu kutumia kamera kwa wakati wowote bila uthibitisho wako."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"zima LED ya kisambaza kiashirio wakati kamera inatumika"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index da17704..c5ef006 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"โหมดปลอดภัย"</string>
     <string name="android_system_label" msgid="6577375335728551336">"ระบบแอนดรอยด์"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"ส่วนตัว"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"ที่ทำงาน"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"แอปส่วนตัว"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"แอนดรอยด์สำหรับการทำงาน"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"บริการที่ต้องเสียค่าใช้จ่าย"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"ทำสิ่งที่คุณต้องเสียค่าใช้จ่าย"</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"ข้อความของคุณ"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"อนุญาตให้แอปพลิเคชันปรับเปลี่ยนการตั้งค่าเสียงทั้งหมดได้ เช่น ระดับเสียงและลำโพงที่จะใช้งาน"</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"บันทึกเสียง"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"อนุญาตให้แอปพลิเคชันบันทึกเสียงด้วยไมโครโฟน การอนุญาตนี้ทำให้แอปพลิเคชันสามารถบันทึกเสียงได้ทุกเมื่อโดยไม่ต้องรอการยืนยันจากคุณ"</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"การสื่อสารกับ SIM"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"อนุญาตให้แอปส่งคำสั่งไปยัง SIM ซึ่งอันตรายมาก"</string>
     <string name="permlab_camera" msgid="3616391919559751192">"ถ่ายภาพและวิดีโอ"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"อนุญาตให้แอปพลิเคชันถ่ายภาพและวิดีโอด้วยกล้องถ่ายรูปนี้ การอนุญาตนี้จะทำให้แอปพลิเคชันสามารถใช้กล้องถ่ายรูปได้ทุกเมื่อโดยไม่ต้องรอการยืนยันจากคุณ"</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"ปิดไฟสัญญาณ LED เมื่อใช้งานกล้อง"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 3bbfc0d..84e83d3 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Safe mode"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android System"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Trabaho"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Mga personal na app"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android para sa Trabaho"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Mga serbisyong ginagastusan mo"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Gumawa ng mga bagay na magpapagastos sa iyo."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Iyong mga mensahe"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Pinapayagan ang app na baguhin ang mga pandaigdigang setting ng audio gaya ng volume at kung aling speaker ang ginagamit para sa output."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"mag-record ng audio"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Pinapayagan ang app na mag-record ng audio gamit ang mikropono. Pinapayagan ng pahintulot na ito ang app na mag-record ng audio anumang oras nang wala ng iyong kumpirmasyon."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"pag-uusap sa sim"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Pinapahintulutang magpadala ang app ng mga command sa SIM. Napakapanganib nito."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"kumuha ng mga larawan at video"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Pinapayagan ang app na kumuha ng mga larawan at video gamit ang camera. Pinapayagan ng pahintulot na ito ang app na gamitin ang camera anumang oras nang wala ng iyong kumpirmasyon."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"i-disable ang LED na tagapagpahiwatig kapag ginagamit ang camera"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 76ea9e9..04bec96 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Güvenli mod"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android Sistemi"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Kişisel"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"İş"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Kişisel uygulamalar"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"İş için Android"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Size maliyet getiren hizmetler"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Size maliyet getirebilecek işlemler yapma."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Mesajlarınız"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Uygulamaya ses düzeyi ve ses çıkışı için kullanılan hoparlör gibi genel ses ayarlarını değiştirme izni verir."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"ses kaydet"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Uygulamaya mikrofonla ses kaydetme izni verir. Bu izin, uygulamanın istediği zaman onayınız olmadan ses kaydetmesine olanak sağlar."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"sim iletişimi"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Uygulamanın SIM karta komut göndermesine izin verir. Bu izin çok tehlikelidir."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"resim çekme ve görüntü kaydetme"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Uygulamaya kamerayla fotoğraf ve video çekme izni verir. Bu izin, uygulamanın sizin onayınız olmadan istediği zaman kamerayı kullanmasına olanak sağlar."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"Kamera kullanımda iken iletim göstergesi LED\'ini devre dışı bırak"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 70f6192..648d625 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Безп. режим"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Система Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Особистий профіль"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Службовий профіль"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Особисті додатки"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android для роботи"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Служби, які потребують оплати"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Виконувати дії, які потребують оплати."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Ваші повідомл."</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Дозволяє програмі змінювати загальні налаштування звуку, як-от гучність і динамік, який використовується для виводу сигналу."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"запис-ти аудіо"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Дозволяє програмі записувати звук за допомогою мікрофона. Такий дозвіл дає програмі змогу будь-коли записувати звук без вашого підтвердження."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"комунікація із SIM-картою"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Дозволяє програмі надсилати команди на SIM-карту. Це дуже небезпечно."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"фотограф. та знімати відео"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Дозволяє програмі фотографувати та знімати відео за допомогою камери. Такий дозвіл дає програмі змогу будь-коли використовувати камеру без вашого підтвердження."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"вимикати світлодіодний індикатор передавання, коли використовується камера"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 4675f5d..7ee9070 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Chế độ an toàn"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Hệ thống Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Cá nhân"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Cơ quan"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Ứng dụng cá nhân"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android dành cho công việc"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Dịch vụ tính tiền của bạn"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Thực hiện những tác vụ mà bạn có thể phải trả tiền."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Tin nhắn của bạn"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Cho phép ứng dụng sửa đổi cài đặt âm thanh chung chẳng hạn như âm lượng và loa nào được sử dụng cho thiết bị ra."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"ghi âm"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Cho phép ứng dụng ghi âm bằng micrô. Quyền này cho phép ứng dụng ghi âm bất kỳ lúc nào mà không cần sự xác nhận của bạn."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"liên lạc qua sim"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Cho phép ứng dụng gửi lệnh đến SIM. Việc này rất nguy hiểm."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"chụp ảnh và quay video"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Cho phép ứng dụng chụp ảnh và quay video bằng máy ảnh. Quyền này cho phép ứng dụng sử dụng máy ảnh bất kỳ lúc nào mà không cần sự xác nhận của bạn."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"vô hiệu hóa tính năng phát đèn LED chỉ báo khi máy ảnh đang được sử dụng"</string>
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index 8d82a17..6052fb0 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -36,4 +36,8 @@
     <!-- Maximum velocity to initiate a fling, as measured in dips per second. -->
     <dimen name="config_viewMaxFlingVelocity">8000dp</dimen>
 
+    <!-- Number of notifications to keep in the notification service historical archive.
+         Reduced intentionally for watches to retain minimal memory footprint -->
+    <integer name="config_notificationServiceArchiveSize">1</integer>
+
 </resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 234b3d4..d37eddd 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"安全模式"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android 系统"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"个人"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"企业"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"个人应用"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android for Work"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"需要您付费的服务"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"执行可能需要您付费的操作。"</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"您的信息"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"允许该应用修改全局音频设置,例如音量和用于输出的扬声器。"</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"录音"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"允许该应用使用麦克风录制音频。此权限可让该应用不经您的确认即可随时录制音频。"</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM卡通信"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"允许应用向SIM卡发送命令(此权限具有很高的危险性)。"</string>
     <string name="permlab_camera" msgid="3616391919559751192">"拍摄照片和视频"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"允许该应用使用相机拍摄照片和视频。此权限可让该应用随时使用相机,而无需您的确认。"</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"在相机使用过程中停用传输指示灯 LED"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index b1474e6..a9e1770 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"安全模式"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android 系統"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"個人"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"公司"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"個人應用程式"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android 企業版"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"付費服務"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"執行需付費的操作或服務。"</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"您的訊息"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"允許應用程式修改全域音頻設定,例如音量和用於輸出的喇叭。"</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"錄製音效"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"允許應用程式使用麥克風錄音。這項權限允許應用程式隨時錄音,而不需經您確認。"</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM 卡通訊"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"允許應用程式傳送指令到 SIM 卡。這項操作具有高危險性。"</string>
     <string name="permlab_camera" msgid="3616391919559751192">"拍照和拍攝影片"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"允許應用程式使用相機拍照和錄影。這項權限允許應用程式隨時使用相機,而不需經您確認。"</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"使用相機時停用傳輸指示燈"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 0e7bfa8..b1aaae7 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"超過 999"</string>
     <string name="safeMode" msgid="2788228061547930246">"安全模式"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android 系統"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"個人"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"公司"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"個人應用程式"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"Android 企業版"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"需要額外費用的服務。"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"執行需付費的作業或服務。"</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"您的簡訊"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"允許應用程式修改全域音訊設定,例如音量和用來輸出的喇叭。"</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"錄製音訊"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"允許應用程式使用麥克風錄音。這項權限可讓應用程式隨時錄音,不需經過您的確認。"</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"SIM 卡通訊"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"允許應用程式傳送指令到 SIM 卡。這麼做非常危險。"</string>
     <string name="permlab_camera" msgid="3616391919559751192">"拍攝相片和影片"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"允許應用程式使用相機拍照和錄影。這項權限可讓應用程式隨時使用相機,而不需請求您進行確認。"</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"使用攝影機時停用傳輸指示器 LED"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index ff1bc40..25ff769 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -188,8 +188,8 @@
     <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
     <string name="safeMode" msgid="2788228061547930246">"Imodi ephephile"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Uhlelo lwe-Android"</string>
-    <string name="user_owner_label" msgid="2804351898001038951">"Okomuntu siqu"</string>
-    <string name="managed_profile_label" msgid="6260850669674791528">"Umsebenzi"</string>
+    <string name="user_owner_label" msgid="6465364741001216388">"Izinhlelo zokusebenza zomuntu siqu"</string>
+    <string name="managed_profile_label" msgid="3022906847647343112">"I-Android yomsebenzi"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Amasevisi abiza imali"</string>
     <string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Yenza izinto ezingakudla imali."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Imiyalezo yakho"</string>
@@ -532,6 +532,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Ivumela uhlelo lokusebenza ukushintsha izilungiselelo zomsindo we-global njengevolomu nokuthi isiphi isipika esisetshenziselwa okukhiphayo."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"qopha umsindo"</string>
     <string name="permdesc_recordAudio" msgid="4906839301087980680">"Ivumela uhlelo lokusebenza ukurekhoda umsindo nge-microphone. Le mvume ivumela uhlelo lokusebenza ukuqopha umsindo noma kunini ngaphandle kokuqinisekisa kwakho."</string>
+    <string name="permlab_sim_communication" msgid="1180265879464893029">"uxhumano le-sim"</string>
+    <string name="permdesc_sim_communication" msgid="5725159654279639498">"Ivumela uhlelo lokusebenza ukuthumela imiyalo ku-SIM. Lokhu kuyingozi kakhulu."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"thatha izithombe namavidiyo"</string>
     <string name="permdesc_camera" msgid="8497216524735535009">"Ivumela uhlelo lokusebenza ukuthatha izithombe namavidiyo ngekhamera. Le mvume ivumela uhlelo lokusebenza ukusebenzisa ikhamera nganoma isiphi isikhathi ngaphandle kwemvume yakho."</string>
     <string name="permlab_cameraDisableTransmitLed" msgid="2651072630501126222">"khubaza i-LED yesikhombi sokudlulisa uma ikhamera isebenza"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index ed648fb..5213896 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -496,6 +496,12 @@
         <!-- Internal layout used internally for window decor -->
         <attr name="windowActionBarFullscreenDecorLayout" format="reference" />
 
+        <!-- The duration, in milliseconds, of the window background fade duration
+             when transitioning into or away from an Activity when called with an
+             Activity Transition. Corresponds to
+             {@link android.view.Window#setTransitionBackgroundFadeDuration(long)}. -->
+        <attr name="windowTransitionBackgroundFadeDuration" format="integer"/>
+
         <!-- ============ -->
         <!-- Alert Dialog styles -->
         <!-- ============ -->
@@ -1813,6 +1819,11 @@
              Corresponds to {@link android.view.Window#setNavigationBarColor(int)}. -->
         <attr name="navigationBarColor" format="color" />
 
+        <!-- The duration, in milliseconds, of the window background fade duration
+             when transitioning into or away from an Activity when called with an
+             Activity Transition. Corresponds to
+             {@link android.view.Window#setTransitionBackgroundFadeDuration(long)}. -->
+        <attr name="windowTransitionBackgroundFadeDuration" />
     </declare-styleable>
 
     <!-- The set of attributes that describe a AlertDialog's theme. -->
@@ -3859,8 +3870,11 @@
         <attr name="inputType" />
     </declare-styleable>
     <declare-styleable name="PopupWindow">
+        <!-- The background to use for the popup window. -->
         <attr name="popupBackground" format="reference|color" />
+        <!-- The animation style to use for the popup window. -->
         <attr name="popupAnimationStyle" format="reference" />
+        <!-- Whether the popup window should overlap its anchor view. -->
         <attr name="overlapAnchor" format="boolean" />
     </declare-styleable>
     <declare-styleable name="ViewAnimator">
@@ -4063,6 +4077,9 @@
         The default is one.
         See {@link android.widget.GridLayout.Spec}. -->
         <attr name="layout_rowSpan" format="integer" min="1" />
+        <!-- The relative proportion of horizontal space that should be allocated to this view
+        during excess space distribution. -->
+        <attr name="layout_rowWeight" format="float" />
         <!-- The column boundary delimiting the left of the group of cells
         occupied by this view. -->
         <attr name="layout_column" />
@@ -4071,6 +4088,9 @@
         The default is one.
         See {@link android.widget.GridLayout.Spec}. -->
         <attr name="layout_columnSpan" format="integer" min="1" />
+        <!-- The relative proportion of vertical space that should be allocated to this view
+        during excess space distribution. -->
+        <attr name="layout_columnWeight" format="float" />
         <!-- Gravity specifies how a component should be placed in its group of cells.
         The default is LEFT | BASELINE.
         See {@link android.widget.GridLayout.LayoutParams#setGravity(int)}. -->
@@ -4766,7 +4786,7 @@
     </declare-styleable>
 
     <!-- ========================== -->
-    <!--   Vector drawable class   -->
+    <!--   VectorDrawable class   -->
     <!-- ========================== -->
     <eat-comment />
 
@@ -4786,7 +4806,29 @@
         <attr name="height" />
     </declare-styleable>
 
-    <!-- Defines the path used in Vector Drawables. -->
+    <!-- Defines the group used in VectorDrawables. -->
+    <declare-styleable name="VectorDrawableGroup">
+        <!-- The Name of this group -->
+        <attr name="name" />
+        <!-- The amount to rotate the group -->
+        <attr name="rotation" />
+        <!-- The X coordinate of the center of rotation of a group -->
+        <attr name="pivotX" />
+        <!-- The Y coordinate of the center of rotation of a group -->
+        <attr name="pivotY" />
+        <!-- The amount to translate the group on X coordinate -->
+        <attr name="translateX" format="float"/>
+        <!-- The amount to translate the group on Y coordinate -->
+        <attr name="translateY" format="float"/>
+        <!-- The amount to scale the group on X coordinate -->
+        <attr name="scaleX" />
+        <!-- The amount to scale the group on X coordinate -->
+        <attr name="scaleY" />
+        <!-- The alpha of the group (0 is transparent and 1 is opaque) -->
+        <attr name="alpha" />
+    </declare-styleable>
+
+    <!-- Defines the path used in VectorDrawables. -->
     <declare-styleable name="VectorDrawablePath">
         <!-- The Name of this path -->
         <attr name="name" />
@@ -4794,12 +4836,6 @@
         <attr name="strokeWidth" format="float" />
         <!-- The opacity of a path stroke -->
         <attr name="strokeOpacity" format="float" />
-        <!-- The amount to rotate the path stroke -->
-        <attr name="rotation" />
-        <!-- The X coordinate of the center of rotation of a path -->
-        <attr name="pivotX" />
-        <!-- The Y coordinate of the center of rotation of a path -->
-        <attr name="pivotY" />
         <!-- The color to stroke the path if not defined implies no stroke-->
         <attr name="stroke" format="color" />
         <!-- The color to fill the path if not defined implies no fill-->
@@ -4833,6 +4869,25 @@
     </declare-styleable>
 
     <!-- ========================== -->
+    <!--   AnimatedVectorDrawable class   -->
+    <!-- ========================== -->
+    <eat-comment />
+
+    <!-- Define the AnimatedVectorDrawable. -->
+    <declare-styleable name="AnimatedVectorDrawable">
+        <!-- The static vector drawable. -->
+        <attr name="drawable" />
+    </declare-styleable>
+
+    <!-- Defines the target path or group used in the AnimatedVectorDrawable. -->
+    <declare-styleable name="AnimatedVectorDrawableTarget">
+        <!-- The name of this target path or group -->
+        <attr name="name" />
+        <!-- The animation for this target path or group -->
+        <attr name="animation" />
+    </declare-styleable>
+
+    <!-- ========================== -->
     <!-- Animation class attributes -->
     <!-- ========================== -->
     <eat-comment />
@@ -6126,13 +6181,16 @@
     <!-- Use <code>trust-agent</code> as the root tag of the XML resource that
          describes an {@link android.service.trust.TrustAgentService}, which is
          referenced from its {@link android.service.trust.TrustAgentService#TRUST_AGENT_META_DATA}
-         meta-data entry.  Described here are the attributes that can be included in that tag.
-         @hide -->
+         meta-data entry.  Described here are the attributes that can be included in that tag. -->
     <declare-styleable name="TrustAgent">
         <!-- Component name of an activity that allows the user to modify
-             the settings for this trust agent.
-             @hide -->
+             the settings for this trust agent. -->
         <attr name="settingsActivity" />
+        <!-- Title for a preference that allows that user to launch the
+             activity to modify trust agent settings. -->
+        <attr name="title" />
+        <!-- Summary for the same preference as the title. -->
+        <attr name="summary" />
     </declare-styleable>
 
     <!-- =============================== -->
@@ -6365,6 +6423,16 @@
         <attr name="settingsActivity" />
     </declare-styleable>
 
+    <!-- Use <code>voice-enrollment-application</code>
+         as the root tag of the XML resource that escribes the supported keyphrases (hotwords)
+         by the enrollment application.
+         Described here are the attributes that can be included in that tag. -->
+    <declare-styleable name="VoiceEnrollmentApplication">
+        <attr name="searchKeyphraseId" format="integer" />
+        <attr name="searchKeyphrase" format="string" />
+        <attr name="searchKeyphraseSupportedLocales" format="string" />
+    </declare-styleable>
+
     <!-- Attributes used to style the Action Bar. -->
     <declare-styleable name="ActionBar">
         <!-- The type of navigation to use. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 27ac6c30..9ff9b11 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -628,6 +628,9 @@
     <!-- Default value for LED off time when the battery is low on charge in miliseconds -->
     <integer name="config_notificationsBatteryLedOff">2875</integer>
 
+    <!-- Number of notifications to keep in the notification service historical archive -->
+    <integer name="config_notificationServiceArchiveSize">250</integer>
+
     <!-- Allow the menu hard key to be disabled in LockScreen on some devices -->
     <bool name="config_disableMenuKeyInLockScreen">false</bool>
 
@@ -1446,6 +1449,10 @@
     <string name="config_customAdbPublicKeyConfirmationComponent"
             >com.android.systemui/com.android.systemui.usb.UsbDebuggingActivity</string>
 
+    <!-- Name of the CustomDialog that is used for VPN -->
+    <string name="config_customVpnConfirmDialogComponent"
+            >com.android.vpndialogs/com.android.vpndialogs.CustomDialog</string>
+
     <!-- Apps that are authorized to access shared accounts, overridden by product overlays -->
     <string name="config_appsAuthorizedForSharedAccounts">;com.android.settings;</string>
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index e16082f..202c127 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2179,8 +2179,17 @@
   <public type="attr" name="contentInsetLeft" />
   <public type="attr" name="contentInsetRight" />
   <public type="attr" name="paddingMode" />
+  <public type="attr" name="layout_rowWeight" />
+  <public type="attr" name="layout_columnWeight" />
+  <public type="attr" name="translateX" />
+  <public type="attr" name="translateY" />
   <public type="attr" name="selectableItemBackgroundBorderless" />
   <public type="attr" name="elegantTextHeight" />
+  <public type="attr" name="searchKeyphraseId" />
+  <public type="attr" name="searchKeyphrase" />
+  <public type="attr" name="searchKeyphraseSupportedLocales" />
+  <public type="attr" name="windowTransitionBackgroundFadeDuration" />
+  <public type="attr" name="overlapAnchor" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a224cd5..f1d9dc3 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1122,6 +1122,12 @@
         interface of a voice interaction service. Should never be needed for normal apps.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_manageVoiceKeyphrases">manage voice keyphrases</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_manageVoiceKeyphrases">Allows the holder to manage the keyphrases for voice hotword detection.
+        Should never be needed for normal apps.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_bindRemoteDisplay">bind to a remote display</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_bindRemoteDisplay">Allows the holder to bind to the top-level
@@ -1589,6 +1595,11 @@
       without your confirmation.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_sim_communication">sim communication</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_sim_communication">Allows the app to send commands to the SIM. This is very dangerous.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_camera">take pictures and videos</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_camera">Allows the app to take pictures and videos
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index c769cd9..5bd6122 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -237,14 +237,6 @@
         <item name="windowExitAnimation">@anim/fast_fade_out</item>
     </style>
 
-    <!-- Window animations for swipe-dismissable windows. {@hide} -->
-    <style name="Animation.SwipeDismiss">
-        <item name="taskOpenEnterAnimation">@anim/swipe_window_enter</item>
-        <item name="taskOpenExitAnimation">@anim/swipe_window_exit</item>
-        <item name="taskCloseEnterAnimation">@anim/swipe_window_enter</item>
-        <item name="taskCloseExitAnimation">@anim/swipe_window_exit</item>
-    </style>
-
     <!-- Status Bar Styles -->
     <style name="TextAppearance.StatusBar">
         <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 7120521..ddd82c3 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -624,6 +624,7 @@
 
     <style name="Widget.Material.Spinner.DropDown.ActionBar">
         <item name="background">@drawable/spinner_background_material</item>
+        <item name="overlapAnchor">true</item>
     </style>
 
     <style name="Widget.Material.TabWidget" parent="Widget.TabWidget">
diff --git a/core/res/res/values/styles_micro.xml b/core/res/res/values/styles_micro.xml
index 5bac1f9..0c854d3 100644
--- a/core/res/res/values/styles_micro.xml
+++ b/core/res/res/values/styles_micro.xml
@@ -14,6 +14,19 @@
      limitations under the License.
 -->
 <resources>
+    <style name="Animation.Micro"/>
+
+    <style name="Animation.Micro.Activity" parent="Animation.Holo.Activity">
+        <item name="activityOpenEnterAnimation">@anim/slide_in_micro</item>
+        <item name="activityOpenExitAnimation">@null</item>
+        <item name="activityCloseEnterAnimation">@null</item>
+        <item name="activityCloseExitAnimation">@anim/slide_out_micro</item>
+        <item name="taskOpenEnterAnimation">@anim/slide_in_micro</item>
+        <item name="taskOpenExitAnimation">@null</item>
+        <item name="taskCloseEnterAnimation">@null</item>
+        <item name="taskCloseExitAnimation">@anim/slide_out_micro</item>
+    </style>
+
     <style name="AlertDialog.Micro" parent="AlertDialog.Holo.Light">
         <item name="fullDark">@null</item>
         <item name="topDark">@null</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d8e31ea..f2550ab 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1518,6 +1518,7 @@
   <java-symbol type="integer" name="config_notificationsBatteryLedOn" />
   <java-symbol type="integer" name="config_notificationsBatteryLowARGB" />
   <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
+  <java-symbol type="integer" name="config_notificationServiceArchiveSize" />
   <java-symbol type="integer" name="config_radioScanningTimeout" />
   <java-symbol type="integer" name="config_screenBrightnessSettingMinimum" />
   <java-symbol type="integer" name="config_screenBrightnessSettingMaximum" />
@@ -1647,6 +1648,7 @@
   <java-symbol type="integer" name="config_maximumScreenDimDuration" />
   <java-symbol type="fraction" name="config_maximumScreenDimRatio" />
   <java-symbol type="string" name="config_customAdbPublicKeyConfirmationComponent" />
+  <java-symbol type="string" name="config_customVpnConfirmDialogComponent" />
   <java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
 
   <java-symbol type="layout" name="resolver_list" />
diff --git a/core/res/res/values/themes_micro.xml b/core/res/res/values/themes_micro.xml
index ebdab5b..7e0467b 100644
--- a/core/res/res/values/themes_micro.xml
+++ b/core/res/res/values/themes_micro.xml
@@ -19,15 +19,14 @@
         <item name="alertDialogStyle">@style/AlertDialog.Micro</item>
         <item name="dialogTheme">@style/Theme.Micro.Dialog</item>
         <item name="textViewStyle">@style/Widget.Micro.TextView</item>
-
         <item name="numberPickerStyle">@style/Widget.Micro.NumberPicker</item>
-        <item name="windowAnimationStyle">@style/Animation.SwipeDismiss</item>
+        <item name="windowAnimationStyle">@style/Animation.Micro.Activity</item>
         <item name="windowBackground">@color/black</item>
         <item name="windowContentOverlay">@null</item>
         <item name="windowIsFloating">false</item>
         <item name="windowIsTranslucent">true</item>
         <item name="windowSwipeToDismiss">true</item>
-	</style>
+    </style>
 
     <style name="Theme.Micro.Light" parent="Theme.Holo.Light.NoActionBar">
         <item name="alertDialogTheme">@style/Theme.Micro.Dialog.Alert</item>
@@ -35,7 +34,7 @@
         <item name="dialogTheme">@style/Theme.Micro.Dialog</item>
         <item name="textViewStyle">@style/Widget.Micro.TextView</item>
         <item name="numberPickerStyle">@style/Widget.Micro.NumberPicker</item>
-        <item name="windowAnimationStyle">@style/Animation.SwipeDismiss</item>
+        <item name="windowAnimationStyle">@style/Animation.Micro.Activity</item>
         <item name="windowBackground">@color/white</item>
         <item name="windowContentOverlay">@null</item>
         <item name="windowIsFloating">false</item>
diff --git a/core/tests/coretests/src/android/os/FileBridgeTest.java b/core/tests/coretests/src/android/os/FileBridgeTest.java
new file mode 100644
index 0000000..d4f6b1f
--- /dev/null
+++ b/core/tests/coretests/src/android/os/FileBridgeTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.FileBridge.FileBridgeOutputStream;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+import libcore.io.Streams;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Random;
+
+public class FileBridgeTest extends AndroidTestCase {
+
+    private File file;
+    private FileOutputStream fileOs;
+    private FileBridge bridge;
+    private FileBridgeOutputStream client;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        file = getContext().getFileStreamPath("meow.dat");
+        file.delete();
+
+        fileOs = new FileOutputStream(file);
+
+        bridge = new FileBridge();
+        bridge.setTargetFile(fileOs.getFD());
+        bridge.start();
+        client = new FileBridgeOutputStream(bridge.getClientSocket());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        fileOs.close();
+        file.delete();
+    }
+
+    private void assertOpen() throws Exception {
+        assertFalse("expected open", bridge.isClosed());
+    }
+
+    private void closeAndAssertClosed() throws Exception {
+        client.close();
+
+        // Wait a beat for things to settle down
+        SystemClock.sleep(200);
+        assertTrue("expected closed", bridge.isClosed());
+    }
+
+    private void assertContents(byte[] expected) throws Exception {
+        MoreAsserts.assertEquals(expected, Streams.readFully(new FileInputStream(file)));
+    }
+
+    public void testNoWriteNoSync() throws Exception {
+        assertOpen();
+        closeAndAssertClosed();
+    }
+
+    public void testNoWriteSync() throws Exception {
+        assertOpen();
+        client.flush();
+        closeAndAssertClosed();
+    }
+
+    public void testWriteNoSync() throws Exception {
+        assertOpen();
+        client.write("meow".getBytes(StandardCharsets.UTF_8));
+        closeAndAssertClosed();
+        assertContents("meow".getBytes(StandardCharsets.UTF_8));
+    }
+
+    public void testWriteSync() throws Exception {
+        assertOpen();
+        client.write("cake".getBytes(StandardCharsets.UTF_8));
+        client.flush();
+        closeAndAssertClosed();
+        assertContents("cake".getBytes(StandardCharsets.UTF_8));
+    }
+
+    public void testWriteSyncWrite() throws Exception {
+        assertOpen();
+        client.write("meow".getBytes(StandardCharsets.UTF_8));
+        client.flush();
+        client.write("cake".getBytes(StandardCharsets.UTF_8));
+        closeAndAssertClosed();
+        assertContents("meowcake".getBytes(StandardCharsets.UTF_8));
+    }
+
+    public void testEmptyWrite() throws Exception {
+        assertOpen();
+        client.write(new byte[0]);
+        closeAndAssertClosed();
+        assertContents(new byte[0]);
+    }
+
+    public void testWriteAfterClose() throws Exception {
+        assertOpen();
+        client.write("meow".getBytes(StandardCharsets.UTF_8));
+        closeAndAssertClosed();
+        try {
+            client.write("cake".getBytes(StandardCharsets.UTF_8));
+            fail("wrote after close!");
+        } catch (IOException expected) {
+        }
+        assertContents("meow".getBytes(StandardCharsets.UTF_8));
+    }
+
+    public void testRandomWrite() throws Exception {
+        final Random r = new Random();
+        final ByteArrayOutputStream result = new ByteArrayOutputStream();
+
+        for (int i = 0; i < 512; i++) {
+            final byte[] test = new byte[r.nextInt(24169)];
+            r.nextBytes(test);
+            result.write(test);
+            client.write(test);
+            client.flush();
+        }
+
+        closeAndAssertClosed();
+        assertContents(result.toByteArray());
+    }
+
+    public void testGiantWrite() throws Exception {
+        final byte[] test = new byte[263401];
+        new Random().nextBytes(test);
+
+        assertOpen();
+        client.write(test);
+        closeAndAssertClosed();
+        assertContents(test);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java
index 5dc9ef8..433d4d2 100644
--- a/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.util;
 
+import android.test.MoreAsserts;
+
+import java.util.Arrays;
 import junit.framework.TestCase;
 
 /**
@@ -77,4 +80,79 @@
         assertFalse(ArrayUtils.containsAll(new Object[] { }, new Object[] { null }));
         assertFalse(ArrayUtils.containsAll(new Object[] { A }, new Object[] { null }));
     }
+
+    public void testContainsInt() throws Exception {
+        assertTrue(ArrayUtils.contains(new int[] { 1, 2, 3 }, 1));
+        assertTrue(ArrayUtils.contains(new int[] { 1, 2, 3 }, 2));
+        assertTrue(ArrayUtils.contains(new int[] { 1, 2, 3 }, 3));
+
+        assertFalse(ArrayUtils.contains(new int[] { 1, 2, 3 }, 0));
+        assertFalse(ArrayUtils.contains(new int[] { 1, 2, 3 }, 4));
+        assertFalse(ArrayUtils.contains(new int[] { }, 2));
+    }
+
+    public void testAppendInt() throws Exception {
+        MoreAsserts.assertEquals(new int[] { 1 },
+                ArrayUtils.appendInt(null, 1));
+        MoreAsserts.assertEquals(new int[] { 1 },
+                ArrayUtils.appendInt(new int[] { }, 1));
+        MoreAsserts.assertEquals(new int[] { 1, 2 },
+                ArrayUtils.appendInt(new int[] { 1 }, 2));
+        MoreAsserts.assertEquals(new int[] { 1, 2 },
+                ArrayUtils.appendInt(new int[] { 1, 2 }, 1));
+    }
+
+    public void testRemoveInt() throws Exception {
+        assertNull(ArrayUtils.removeInt(null, 1));
+        MoreAsserts.assertEquals(new int[] { },
+                ArrayUtils.removeInt(new int[] { }, 1));
+        MoreAsserts.assertEquals(new int[] { 1, 2, 3, },
+                ArrayUtils.removeInt(new int[] { 1, 2, 3}, 4));
+        MoreAsserts.assertEquals(new int[] { 2, 3, },
+                ArrayUtils.removeInt(new int[] { 1, 2, 3}, 1));
+        MoreAsserts.assertEquals(new int[] { 1, 3, },
+                ArrayUtils.removeInt(new int[] { 1, 2, 3}, 2));
+        MoreAsserts.assertEquals(new int[] { 1, 2, },
+                ArrayUtils.removeInt(new int[] { 1, 2, 3}, 3));
+        MoreAsserts.assertEquals(new int[] { 2, 3, 1 },
+                ArrayUtils.removeInt(new int[] { 1, 2, 3, 1 }, 1));
+    }
+
+    public void testContainsLong() throws Exception {
+        assertTrue(ArrayUtils.contains(new long[] { 1, 2, 3 }, 1));
+        assertTrue(ArrayUtils.contains(new long[] { 1, 2, 3 }, 2));
+        assertTrue(ArrayUtils.contains(new long[] { 1, 2, 3 }, 3));
+
+        assertFalse(ArrayUtils.contains(new long[] { 1, 2, 3 }, 0));
+        assertFalse(ArrayUtils.contains(new long[] { 1, 2, 3 }, 4));
+        assertFalse(ArrayUtils.contains(new long[] { }, 2));
+    }
+
+    public void testAppendLong() throws Exception {
+        MoreAsserts.assertEquals(new long[] { 1 },
+                ArrayUtils.appendLong(null, 1));
+        MoreAsserts.assertEquals(new long[] { 1 },
+                ArrayUtils.appendLong(new long[] { }, 1));
+        MoreAsserts.assertEquals(new long[] { 1, 2 },
+                ArrayUtils.appendLong(new long[] { 1 }, 2));
+        MoreAsserts.assertEquals(new long[] { 1, 2 },
+                ArrayUtils.appendLong(new long[] { 1, 2 }, 1));
+    }
+
+    public void testRemoveLong() throws Exception {
+        assertNull(ArrayUtils.removeLong(null, 1));
+        MoreAsserts.assertEquals(new long[] { },
+                ArrayUtils.removeLong(new long[] { }, 1));
+        MoreAsserts.assertEquals(new long[] { 1, 2, 3, },
+                ArrayUtils.removeLong(new long[] { 1, 2, 3}, 4));
+        MoreAsserts.assertEquals(new long[] { 2, 3, },
+                ArrayUtils.removeLong(new long[] { 1, 2, 3}, 1));
+        MoreAsserts.assertEquals(new long[] { 1, 3, },
+                ArrayUtils.removeLong(new long[] { 1, 2, 3}, 2));
+        MoreAsserts.assertEquals(new long[] { 1, 2, },
+                ArrayUtils.removeLong(new long[] { 1, 2, 3}, 3));
+        MoreAsserts.assertEquals(new long[] { 2, 3, 1 },
+                ArrayUtils.removeLong(new long[] { 1, 2, 3, 1 }, 1));
+    }
+
 }
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
new file mode 100644
index 0000000..d649154
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
@@ -0,0 +1,42 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+
+## The application with a minimal main dex
+include $(CLEAR_VARS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := MultiDexLegacyAndException
+
+mainDexList:= \
+	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
+
+LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
+
+include $(BUILD_PACKAGE)
+
+$(mainDexList): $(full_classes_proguard_jar) | $(HOST_OUT_EXECUTABLES)/mainDexClasses
+	$(HOST_OUT_EXECUTABLES)/mainDexClasses $< 1>$@
+	echo "com/android/multidexlegacyandexception/Test.class" >> $@
+
+$(built_dex_intermediate): $(mainDexList)
+
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml
new file mode 100644
index 0000000..7fff711
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.multidexlegacyandexception"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18"/>
+
+    <application
+        android:name="com.android.multidexlegacyandexception.TestApplication"
+        android:label="multidexlegacyandexception"
+        >
+        <activity
+            android:name="com.android.multidexlegacyandexception.MainActivity"
+            android:label="multidexlegacyandexception" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+                     android:targetPackage="com.android.multidexlegacyandexception"
+                     android:label="Test for MultiDexLegacyAndException" />
+</manifest>
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/layout/activity_main.xml b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/layout/activity_main.xml
new file mode 100644
index 0000000..37eb613
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/layout/activity_main.xml
@@ -0,0 +1,13 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    tools:context=".MainActivity" >
+
+    <TextView
+        android:id="@+id/label_nb"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/label_nb" />
+
+</RelativeLayout>
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/values/strings.xml b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/values/strings.xml
new file mode 100644
index 0000000..e56e049
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/values/strings.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="app_name">MultidexLegacyAndException</string>
+    <string name="action_settings">Settings</string>
+    <string name="label_nb">Here\'s the count: </string>
+
+</resources>
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyByIntermediateException.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyByIntermediateException.java
new file mode 100644
index 0000000..d6883ec
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyByIntermediateException.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.multidexlegacyandexception;
+
+public class CaughtOnlyByIntermediateException extends RuntimeException {
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyException.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyException.java
new file mode 100644
index 0000000..4903e01
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyException.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.multidexlegacyandexception;
+
+public class CaughtOnlyException extends RuntimeException {
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ClassInSecondaryDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ClassInSecondaryDex.java
new file mode 100644
index 0000000..b08a11a
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ClassInSecondaryDex.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.multidexlegacyandexception;
+
+public class ClassInSecondaryDex {
+    private boolean condition;
+
+    public ClassInSecondaryDex(boolean condition) {
+        this.condition = condition;
+    }
+
+    public void canThrow1() throws ExceptionInMainDex, ExceptionInMainDex2,
+            ExceptionInSecondaryDexWithSuperInMain {
+        if (condition) {
+            throw new ExceptionInMainDex();
+        }
+    }
+
+    public void canThrow2() throws ExceptionInSecondaryDex, ExceptionInSecondaryDex2,
+            ExceptionInSecondaryDexWithSuperInMain {
+        if (condition) {
+            throw new ExceptionInSecondaryDex();
+        }
+    }
+
+    public static void canThrowAll(Throwable toThrow) throws Throwable {
+        if (toThrow != null) {
+            throw toThrow;
+        }
+    }
+
+    public int get1() {
+        try {
+            canThrow1();
+            canThrow2();
+            return 1;
+        } catch (ExceptionInMainDex e) {
+            return 10;
+        } catch (ExceptionInSecondaryDex e) {
+            return 11;
+        } catch (OutOfMemoryError e) {
+            return 12;
+        } catch (CaughtOnlyException e) {
+            return 17;
+        } catch (SuperExceptionInSecondaryDex|SuperExceptionInMainDex e) {
+            return 23;
+       }
+    }
+
+    public int get2() {
+        try {
+            canThrow2();
+            canThrow1();
+            return 1;
+        } catch (ExceptionInMainDex e) {
+            return 10;
+        } catch (ExceptionInSecondaryDex e) {
+            return 11;
+        } catch (OutOfMemoryError e) {
+            return 12;
+        } catch (CaughtOnlyException e) {
+            return 17;
+        } catch (SuperExceptionInSecondaryDex e) {
+            return 23;
+        } catch (SuperExceptionInMainDex e) {
+            return 27;
+       }
+    }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex.java
new file mode 100644
index 0000000..7fc3d73
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.multidexlegacyandexception;
+
+public class ExceptionInMainDex extends SuperExceptionInMainDex {
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex2.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex2.java
new file mode 100644
index 0000000..3fbeac6
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex2.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.multidexlegacyandexception;
+
+public class ExceptionInMainDex2 extends SuperExceptionInMainDex {
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex.java
new file mode 100644
index 0000000..9401c05
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.multidexlegacyandexception;
+
+public class ExceptionInSecondaryDex extends SuperExceptionInSecondaryDex {
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex2.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex2.java
new file mode 100644
index 0000000..d1aa103
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex2.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.multidexlegacyandexception;
+
+public class ExceptionInSecondaryDex2 extends SuperExceptionInSecondaryDex {
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDexWithSuperInMain.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDexWithSuperInMain.java
new file mode 100644
index 0000000..9327882
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDexWithSuperInMain.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.multidexlegacyandexception;
+
+public class ExceptionInSecondaryDexWithSuperInMain extends SuperExceptionInMainDex {
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/IntermediateClass.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/IntermediateClass.java
new file mode 100644
index 0000000..dfdc4af
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/IntermediateClass.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.multidexlegacyandexception;
+
+public class IntermediateClass {
+
+    public static int get1(boolean condition) {
+        return new ClassInSecondaryDex(condition).get1();
+    }
+
+    public static int get2(boolean condition) {
+        return new ClassInSecondaryDex(condition).get2();
+    }
+
+    public static int get3(boolean condition) {
+        ClassInSecondaryDex thrower = new ClassInSecondaryDex(condition);
+        try {
+            thrower.canThrow2();
+            thrower.canThrow1();
+            return 1;
+        } catch (ExceptionInMainDex e) {
+            return 10;
+        } catch (ExceptionInSecondaryDex e) {
+            return 11;
+        } catch (ExceptionInMainDex2 e) {
+            return 10;
+        } catch (ExceptionInSecondaryDex2 e) {
+            return 11;
+        } catch (OutOfMemoryError e) {
+            return 12;
+        } catch (CaughtOnlyException e) {
+            return 17;
+        } catch (ExceptionInSecondaryDexWithSuperInMain e) {
+            return 39;
+        } catch (SuperExceptionInSecondaryDex|SuperExceptionInMainDex|CaughtOnlyByIntermediateException e) {
+            return 23;
+       }
+    }
+
+    public static int get4(boolean condition) {
+        ClassInSecondaryDex thrower = new ClassInSecondaryDex(condition);
+        try {
+            thrower.canThrow2();
+            thrower.canThrow1();
+            return 1;
+        } catch (ExceptionInSecondaryDexWithSuperInMain e) {
+            return 39;
+        } catch (ExceptionInSecondaryDex e) {
+            return 11;
+        } catch (ExceptionInSecondaryDex2 e) {
+            return 11;
+        } catch (OutOfMemoryError e) {
+            return 12;
+        } catch (ExceptionInMainDex2 e) {
+            return 10;
+        } catch (ExceptionInMainDex e) {
+            return 10;
+        } catch (CaughtOnlyException e) {
+            return 17;
+        } catch (SuperExceptionInSecondaryDex e) {
+        } catch (SuperExceptionInMainDex e) {
+        } catch (CaughtOnlyByIntermediateException e) {
+            return 35;
+        }
+        return 39;
+    }
+
+
+    public static int get5(Throwable thrown) {
+        try {
+            ClassInSecondaryDex.canThrowAll(thrown);
+            return 1;
+        } catch (ExceptionInMainDex e) {
+            return 10;
+        } catch (ExceptionInSecondaryDex e) {
+            return 11;
+        } catch (ExceptionInMainDex2 e) {
+            return 12;
+        } catch (ExceptionInSecondaryDex2 e) {
+            return 13;
+        } catch (OutOfMemoryError e) {
+            return 14;
+        } catch (CaughtOnlyException e) {
+            return 17;
+        } catch (ExceptionInSecondaryDexWithSuperInMain e) {
+            return 39;
+        } catch (SuperExceptionInSecondaryDex|SuperExceptionInMainDex|CaughtOnlyByIntermediateException e) {
+            return 23;
+       } catch (Throwable e) {
+            return 37;
+        }
+    }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MainActivity.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MainActivity.java
new file mode 100644
index 0000000..dd2ce7a
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MainActivity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.multidexlegacyandexception;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity extends Activity {
+
+    public MainActivity() {
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+    }
+
+    public int get1(boolean condition) {
+        return IntermediateClass.get1(condition);
+    }
+
+    public int get2(boolean condition) {
+        return IntermediateClass.get2(condition);
+    }
+    public int get3(boolean condition) {
+        return MiniIntermediateClass.get3(condition);
+    }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MiniIntermediateClass.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MiniIntermediateClass.java
new file mode 100644
index 0000000..5957662
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MiniIntermediateClass.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.multidexlegacyandexception;
+
+public class MiniIntermediateClass {
+
+    public static int get3(boolean condition) {
+        ClassInSecondaryDex thrower = new ClassInSecondaryDex(condition);
+        try {
+            thrower.canThrow2();
+            thrower.canThrow1();
+            return 1;
+        } catch (ExceptionInMainDex e) {
+            return 10;
+        } catch (ExceptionInSecondaryDex e) {
+            return 11;
+        } catch (SuperExceptionInSecondaryDex|SuperExceptionInMainDex e) {
+            return 23;
+        }
+    }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInMainDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInMainDex.java
new file mode 100644
index 0000000..c94b30a
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInMainDex.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.multidexlegacyandexception;
+
+public class SuperExceptionInMainDex extends Exception {
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInSecondaryDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInSecondaryDex.java
new file mode 100644
index 0000000..6366fae
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInSecondaryDex.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.multidexlegacyandexception;
+
+public class SuperExceptionInSecondaryDex extends Exception {
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java
new file mode 100644
index 0000000..5e931bc
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.multidexlegacyandexception;
+
+import android.test.ActivityInstrumentationTestCase2;
+
+/**
+ * Run the tests with: <code>adb shell am instrument -w
+ com.android.multidexlegacyandexception/android.test.InstrumentationTestRunner
+</code>
+ */
+public class Test extends ActivityInstrumentationTestCase2<MainActivity> {
+    public Test() {
+        super(MainActivity.class);
+    }
+
+    public void testExceptionInMainDex() {
+        assertEquals(10, TestApplication.get(true));
+    }
+
+    public void testExceptionInSecondaryDex() {
+        assertEquals(10, getActivity().get1(true));
+        assertEquals(11, getActivity().get2(true));
+    }
+
+    public void testExceptionInIntermediate() {
+        assertEquals(11, IntermediateClass.get3(true));
+        assertEquals(11, MiniIntermediateClass.get3(true));
+        assertEquals(11, IntermediateClass.get4(true));
+        assertEquals(1, IntermediateClass.get5(null));
+        assertEquals(10, IntermediateClass.get5(new ExceptionInMainDex()));
+        assertEquals(11, IntermediateClass.get5(new ExceptionInSecondaryDex()));
+        assertEquals(12, IntermediateClass.get5(new ExceptionInMainDex2()));
+        assertEquals(13, IntermediateClass.get5(new ExceptionInSecondaryDex2()));
+        assertEquals(14, IntermediateClass.get5(new OutOfMemoryError()));
+        assertEquals(17, IntermediateClass.get5(new CaughtOnlyException()));
+        assertEquals(39, IntermediateClass.get5(new ExceptionInSecondaryDexWithSuperInMain()));
+        assertEquals(23, IntermediateClass.get5(new SuperExceptionInSecondaryDex()));
+        assertEquals(23, IntermediateClass.get5(new SuperExceptionInMainDex()));
+        assertEquals(23, IntermediateClass.get5(new CaughtOnlyByIntermediateException()));
+        assertEquals(37, IntermediateClass.get5(new ArrayIndexOutOfBoundsException()));
+    }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/TestApplication.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/TestApplication.java
new file mode 100644
index 0000000..dece9a4
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/TestApplication.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.multidexlegacyandexception;
+
+import android.support.multidex.MultiDexApplication;
+
+public class TestApplication extends MultiDexApplication {
+
+    private static void canThrow1(boolean condition) throws ExceptionInMainDex {
+        if (condition) {
+            throw new ExceptionInMainDex();
+        }
+    }
+
+
+    public static int get(boolean condition) {
+        try {
+            canThrow1(condition);
+            return 1;
+        } catch (ExceptionInMainDex e) {
+            return 10;
+        } catch (OutOfMemoryError e) {
+            return 12;
+        } catch (CaughtOnlyException e) {
+            return 17;
+        } catch (SuperExceptionInMainDex e) {
+            return 27;
+      }
+    }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java
index 20fe465..7b83999 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java
@@ -31,7 +31,7 @@
  * Empty service for testing legacy multidex. Access more than 64k methods but some are required at
  * init, some only at verification and others during execution.
  */
-public abstract class AbstractService extends Service {
+public abstract class AbstractService extends Service implements Runnable {
     private final String TAG = "MultidexLegacyTestService" + getId();
 
     private int instanceFieldNotInited;
@@ -47,6 +47,12 @@
     @Override
     public void onCreate() {
         Log.i(TAG, "onCreate");
+        new Thread(this).start();
+
+    }
+
+    @Override
+    public void run() {
         Context applicationContext = getApplicationContext();
         File resultFile = new File(applicationContext.getFilesDir(), getId());
         try {
@@ -84,7 +90,6 @@
         } catch (IOException e) {
             e.printStackTrace();
         }
-
     }
 
     @Override
diff --git a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java
index 0f343b1..ca68e93 100644
--- a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java
+++ b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java
@@ -25,8 +25,9 @@
 import android.view.inputmethod.InputMethodSubtype;
 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
 
-import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController;
+import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl;
 import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
+import com.android.internal.inputmethod.InputMethodUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -39,6 +40,7 @@
     private static final boolean DUMMY_FORCE_DEFAULT = false;
     private static final int DUMMY_IS_DEFAULT_RES_ID = 0;
     private static final String SYSTEM_LOCALE = "en_US";
+    private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
 
     private static InputMethodSubtype createDummySubtype(final String locale) {
         final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
@@ -64,142 +66,233 @@
         si.exported = true;
         si.nonLocalizedLabel = imeLabel;
         ri.serviceInfo = si;
-        final List<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
-        for (String subtypeLocale : subtypeLocales) {
-            subtypes.add(createDummySubtype(subtypeLocale));
+        List<InputMethodSubtype> subtypes = null;
+        if (subtypeLocales != null) {
+            subtypes = new ArrayList<InputMethodSubtype>();
+            for (String subtypeLocale : subtypeLocales) {
+                subtypes.add(createDummySubtype(subtypeLocale));
+            }
         }
         final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME,
                 DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID,
                 DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod);
-        for (int i = 0; i < subtypes.size(); ++i) {
-            final String subtypeLocale = subtypeLocales.get(i);
-            items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale,
-                    SYSTEM_LOCALE));
+        if (subtypes == null) {
+            items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi,
+                    NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE));
+        } else {
+            for (int i = 0; i < subtypes.size(); ++i) {
+                final String subtypeLocale = subtypeLocales.get(i);
+                items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale,
+                        SYSTEM_LOCALE));
+            }
         }
     }
 
-    private static List<ImeSubtypeListItem> createTestData() {
+    private static List<ImeSubtypeListItem> createEnabledImeSubtypes() {
         final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
-        addDummyImeSubtypeListItems(items, "switchAwareLatinIme", "switchAwareLatinIme",
-                Arrays.asList("en_US", "es_US", "fr"),
+        addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"),
                 true /* supportsSwitchingToNextInputMethod*/);
-        addDummyImeSubtypeListItems(items, "nonSwitchAwareLatinIme", "nonSwitchAwareLatinIme",
+        addDummyImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme",
                 Arrays.asList("en_UK", "hi"),
                 false /* supportsSwitchingToNextInputMethod*/);
-        addDummyImeSubtypeListItems(items, "switchAwareJapaneseIme", "switchAwareJapaneseIme",
-                Arrays.asList("ja_JP"),
+        addDummyImeSubtypeListItems(items, "subtypeUnawareIme", "subtypeUnawareIme", null,
+                false /* supportsSwitchingToNextInputMethod*/);
+        addDummyImeSubtypeListItems(items, "JapaneseIme", "JapaneseIme", Arrays.asList("ja_JP"),
                 true /* supportsSwitchingToNextInputMethod*/);
-        addDummyImeSubtypeListItems(items, "nonSwitchAwareJapaneseIme", "nonSwitchAwareJapaneseIme",
-                Arrays.asList("ja_JP"),
+        addDummyImeSubtypeListItems(items, "switchUnawareJapaneseIme", "switchUnawareJapaneseIme",
+                Arrays.asList("ja_JP"), false /* supportsSwitchingToNextInputMethod*/);
+        return items;
+    }
+
+    private static List<ImeSubtypeListItem> createDisabledImeSubtypes() {
+        final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
+        addDummyImeSubtypeListItems(items,
+                "UnknownIme", "UnknownIme",
+                Arrays.asList("en_US", "hi"),
+                true /* supportsSwitchingToNextInputMethod*/);
+        addDummyImeSubtypeListItems(items,
+                "UnknownSwitchingUnawareIme", "UnknownSwitchingUnawareIme",
+                Arrays.asList("en_US"),
+                false /* supportsSwitchingToNextInputMethod*/);
+        addDummyImeSubtypeListItems(items, "UnknownSubtypeUnawareIme",
+                "UnknownSubtypeUnawareIme", null,
                 false /* supportsSwitchingToNextInputMethod*/);
         return items;
     }
 
-    @SmallTest
-    public void testGetNextInputMethodImplWithNotOnlyCurrentIme() throws Exception {
-        final List<ImeSubtypeListItem> imList = createTestData();
+    private void assertNextInputMethod(final ControllerImpl controller,
+            final boolean onlyCurrentIme,
+            final ImeSubtypeListItem currentItem, final ImeSubtypeListItem nextItem) {
+        InputMethodSubtype subtype = null;
+        if (currentItem.mSubtypeName != null) {
+            subtype = createDummySubtype(currentItem.mSubtypeName.toString());
+        }
+        final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme,
+                currentItem.mImi, subtype);
+        assertEquals(nextItem, nextIme);
+    }
 
-        final boolean ONLY_CURRENT_IME = false;
-        ImeSubtypeListItem currentIme;
-        ImeSubtypeListItem nextIme;
+    private void assertRotationOrder(final ControllerImpl controller,
+            final boolean onlyCurrentIme,
+            final ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) {
+        final int N = expectedRotationOrderOfImeSubtypeList.length;
+        for (int i = 0; i < N; i++) {
+            final int currentIndex = i;
+            final int nextIndex = (currentIndex + 1) % N;
+            final ImeSubtypeListItem currentItem =
+                    expectedRotationOrderOfImeSubtypeList[currentIndex];
+            final ImeSubtypeListItem nextItem = expectedRotationOrderOfImeSubtypeList[nextIndex];
+            assertNextInputMethod(controller, onlyCurrentIme, currentItem, nextItem);
+        }
+    }
 
-        // "switchAwareLatinIme/en_US" -> "switchAwareLatinIme/es_US"
-        currentIme = imList.get(0);
-        nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl(
-                imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype(
-                        currentIme.mSubtypeName.toString()));
-        assertEquals(imList.get(1), nextIme);
-        // "switchAwareLatinIme/es_US" -> "switchAwareLatinIme/fr"
-        currentIme = imList.get(1);
-        nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl(
-                imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype(
-                        currentIme.mSubtypeName.toString()));
-        assertEquals(imList.get(2), nextIme);
-        // "switchAwareLatinIme/fr" -> "switchAwareJapaneseIme/ja_JP"
-        currentIme = imList.get(2);
-        nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl(
-                imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype(
-                        currentIme.mSubtypeName.toString()));
-        assertEquals(imList.get(5), nextIme);
-        // "switchAwareJapaneseIme/ja_JP" -> "switchAwareLatinIme/en_US"
-        currentIme = imList.get(5);
-        nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl(
-                imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype(
-                        currentIme.mSubtypeName.toString()));
-        assertEquals(imList.get(0), nextIme);
-
-        // "nonSwitchAwareLatinIme/en_UK" -> "nonSwitchAwareLatinIme/hi"
-        currentIme = imList.get(3);
-        nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl(
-                imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype(
-                        currentIme.mSubtypeName.toString()));
-        assertEquals(imList.get(4), nextIme);
-        // "nonSwitchAwareLatinIme/hi" -> "nonSwitchAwareJapaneseIme/ja_JP"
-        currentIme = imList.get(4);
-        nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl(
-                imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype(
-                        currentIme.mSubtypeName.toString()));
-        assertEquals(imList.get(6), nextIme);
-        // "nonSwitchAwareJapaneseIme/ja_JP" -> "nonSwitchAwareLatinIme/en_UK"
-        currentIme = imList.get(6);
-        nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl(
-                imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype(
-                        currentIme.mSubtypeName.toString()));
-        assertEquals(imList.get(3), nextIme);
+    private void onUserAction(final ControllerImpl controller,
+            final ImeSubtypeListItem subtypeListItem) {
+        InputMethodSubtype subtype = null;
+        if (subtypeListItem.mSubtypeName != null) {
+            subtype = createDummySubtype(subtypeListItem.mSubtypeName.toString());
+        }
+        controller.onUserActionLocked(subtypeListItem.mImi, subtype);
     }
 
     @SmallTest
-    public void testGetNextInputMethodImplWithOnlyCurrentIme() throws Exception {
-        final List<ImeSubtypeListItem> imList = createTestData();
+    public void testControllerImpl() throws Exception {
+        final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes();
+        final ImeSubtypeListItem disabledIme_en_US = disabledItems.get(0);
+        final ImeSubtypeListItem disabledIme_hi = disabledItems.get(1);
+        final ImeSubtypeListItem disabledSwitchingUnawareIme = disabledItems.get(2);
+        final ImeSubtypeListItem disabledSubtypeUnawareIme = disabledItems.get(3);
 
-        final boolean ONLY_CURRENT_IME = true;
-        ImeSubtypeListItem currentIme;
-        ImeSubtypeListItem nextIme;
+        final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes();
+        final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0);
+        final ImeSubtypeListItem latinIme_fr = enabledItems.get(1);
+        final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2);
+        final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3);
+        final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4);
+        final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5);
+        final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6);
 
-        // "switchAwareLatinIme/en_US" -> "switchAwareLatinIme/es_US"
-        currentIme = imList.get(0);
-        nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl(
-                imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype(
-                        currentIme.mSubtypeName.toString()));
-        assertEquals(imList.get(1), nextIme);
-        // "switchAwareLatinIme/es_US" -> "switchAwareLatinIme/fr"
-        currentIme = imList.get(1);
-        nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl(
-                imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype(
-                        currentIme.mSubtypeName.toString()));
-        assertEquals(imList.get(2), nextIme);
-        // "switchAwareLatinIme/fr" -> "switchAwareLatinIme/en_US"
-        currentIme = imList.get(2);
-        nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl(
-                imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype(
-                        currentIme.mSubtypeName.toString()));
-        assertEquals(imList.get(0), nextIme);
+        final ControllerImpl controller = ControllerImpl.createFrom(
+                null /* currentInstance */, enabledItems);
 
-        // "nonSwitchAwareLatinIme/en_UK" -> "nonSwitchAwareLatinIme/hi"
-        currentIme = imList.get(3);
-        nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl(
-                imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype(
-                        currentIme.mSubtypeName.toString()));
-        assertEquals(imList.get(4), nextIme);
-        // "nonSwitchAwareLatinIme/hi" -> "switchAwareLatinIme/en_UK"
-        currentIme = imList.get(4);
-        nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl(
-                imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype(
-                        currentIme.mSubtypeName.toString()));
-        assertEquals(imList.get(3), nextIme);
+        // switching-aware loop
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                latinIme_en_US, latinIme_fr, japaneseIme_ja_JP);
 
-        // "switchAwareJapaneseIme/ja_JP" -> null
-        currentIme = imList.get(5);
-        nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl(
-                imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype(
-                        currentIme.mSubtypeName.toString()));
-        assertNull(nextIme);
+        // switching-unaware loop
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
+                switchUnawareJapaneseIme_ja_JP);
 
-        // "nonSwitchAwareJapaneseIme/ja_JP" -> null
-        currentIme = imList.get(6);
-        nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl(
-                imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype(
-                        currentIme.mSubtypeName.toString()));
-        assertNull(nextIme);
+        // test onlyCurrentIme == true
+        assertRotationOrder(controller, true /* onlyCurrentIme */,
+                latinIme_en_US, latinIme_fr);
+        assertRotationOrder(controller, true /* onlyCurrentIme */,
+                switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                subtypeUnawareIme, null);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                japaneseIme_ja_JP, null);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                switchUnawareJapaneseIme_ja_JP, null);
+
+        // Make sure that disabled IMEs are not accepted.
+        assertNextInputMethod(controller, false /* onlyCurrentIme */,
+                disabledIme_en_US, null);
+        assertNextInputMethod(controller, false /* onlyCurrentIme */,
+                disabledIme_hi, null);
+        assertNextInputMethod(controller, false /* onlyCurrentIme */,
+                disabledSwitchingUnawareIme, null);
+        assertNextInputMethod(controller, false /* onlyCurrentIme */,
+                disabledSubtypeUnawareIme, null);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                disabledIme_en_US, null);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                disabledIme_hi, null);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                disabledSwitchingUnawareIme, null);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                disabledSubtypeUnawareIme, null);
     }
- }
+
+    @SmallTest
+    public void testControllerImplWithUserAction() throws Exception {
+        final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes();
+        final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0);
+        final ImeSubtypeListItem latinIme_fr = enabledItems.get(1);
+        final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2);
+        final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3);
+        final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4);
+        final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5);
+        final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6);
+
+        final ControllerImpl controller = ControllerImpl.createFrom(
+                null /* currentInstance */, enabledItems);
+
+        // === switching-aware loop ===
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                latinIme_en_US, latinIme_fr, japaneseIme_ja_JP);
+        // Then notify that a user did something for latinIme_fr.
+        onUserAction(controller, latinIme_fr);
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                latinIme_fr, latinIme_en_US, japaneseIme_ja_JP);
+        // Then notify that a user did something for latinIme_fr again.
+        onUserAction(controller, latinIme_fr);
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                latinIme_fr, latinIme_en_US, japaneseIme_ja_JP);
+        // Then notify that a user did something for japaneseIme_ja_JP.
+        onUserAction(controller, latinIme_fr);
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                japaneseIme_ja_JP, latinIme_fr, latinIme_en_US);
+        // Check onlyCurrentIme == true.
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                japaneseIme_ja_JP, null);
+        assertRotationOrder(controller, true /* onlyCurrentIme */,
+                latinIme_fr, latinIme_en_US);
+        assertRotationOrder(controller, true /* onlyCurrentIme */,
+                latinIme_en_US, latinIme_fr);
+
+        // === switching-unaware loop ===
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
+                switchUnawareJapaneseIme_ja_JP);
+        // User action should be ignored for switching unaware IMEs.
+        onUserAction(controller, switchingUnawarelatinIme_hi);
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
+                switchUnawareJapaneseIme_ja_JP);
+        // User action should be ignored for switching unaware IMEs.
+        onUserAction(controller, switchUnawareJapaneseIme_ja_JP);
+        assertRotationOrder(controller, false /* onlyCurrentIme */,
+                switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
+                switchUnawareJapaneseIme_ja_JP);
+        // Check onlyCurrentIme == true.
+        assertRotationOrder(controller, true /* onlyCurrentIme */,
+                switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                subtypeUnawareIme, null);
+        assertNextInputMethod(controller, true /* onlyCurrentIme */,
+                switchUnawareJapaneseIme_ja_JP, null);
+
+        // Rotation order should be preserved when created with the same subtype list.
+        final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes();
+        final ControllerImpl newController = ControllerImpl.createFrom(controller,
+                sameEnabledItems);
+        assertRotationOrder(newController, false /* onlyCurrentIme */,
+                japaneseIme_ja_JP, latinIme_fr, latinIme_en_US);
+        assertRotationOrder(newController, false /* onlyCurrentIme */,
+                switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
+                switchUnawareJapaneseIme_ja_JP);
+
+        // Rotation order should be initialized when created with a different subtype list.
+        final List<ImeSubtypeListItem> differentEnabledItems = Arrays.asList(
+                latinIme_en_US, latinIme_fr, switchingUnawarelatinIme_en_UK,
+                switchUnawareJapaneseIme_ja_JP);
+        final ControllerImpl anotherController = ControllerImpl.createFrom(controller,
+                differentEnabledItems);
+        assertRotationOrder(anotherController, false /* onlyCurrentIme */,
+                latinIme_en_US, latinIme_fr);
+        assertRotationOrder(anotherController, false /* onlyCurrentIme */,
+                switchingUnawarelatinIme_en_UK, switchUnawareJapaneseIme_ja_JP);
+    }
+}
diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk
index c6bccfe..abb960c 100644
--- a/data/fonts/Android.mk
+++ b/data/fonts/Android.mk
@@ -106,6 +106,7 @@
 ifeq ($(MINIMAL_FONT_FOOTPRINT),true)
 
 $(eval $(call create-font-symlink,Roboto-Black.ttf,Roboto-Bold.ttf))
+$(eval $(call create-font-symlink,Roboto-BlackItalic.ttf,Roboto-BoldItalic.ttf))
 $(eval $(call create-font-symlink,Roboto-Light.ttf,Roboto-Regular.ttf))
 $(eval $(call create-font-symlink,Roboto-LightItalic.ttf,Roboto-Italic.ttf))
 $(eval $(call create-font-symlink,Roboto-Medium.ttf,Roboto-Regular.ttf))
@@ -120,6 +121,7 @@
 else # !MINIMAL_FONT
 font_src_files += \
     Roboto-Black.ttf \
+    Roboto-BlackItalic.ttf \
     Roboto-Light.ttf \
     Roboto-LightItalic.ttf \
     Roboto-Medium.ttf \
diff --git a/data/fonts/Roboto-BlackItalic.ttf b/data/fonts/Roboto-BlackItalic.ttf
new file mode 100644
index 0000000..3ebdc7d
--- /dev/null
+++ b/data/fonts/Roboto-BlackItalic.ttf
Binary files differ
diff --git a/data/fonts/fonts.mk b/data/fonts/fonts.mk
index e5573bb..70fc6a2 100644
--- a/data/fonts/fonts.mk
+++ b/data/fonts/fonts.mk
@@ -25,6 +25,7 @@
     Roboto-Italic.ttf \
     Roboto-BoldItalic.ttf \
     Roboto-Black.ttf \
+    Roboto-BlackItalic.ttf \
     Roboto-Light.ttf \
     Roboto-LightItalic.ttf \
     Roboto-Medium.ttf \
diff --git a/data/fonts/system_fonts.xml b/data/fonts/system_fonts.xml
index 646b33b..8c59fea 100644
--- a/data/fonts/system_fonts.xml
+++ b/data/fonts/system_fonts.xml
@@ -82,6 +82,7 @@
         </nameset>
         <fileset>
             <file>Roboto-Black.ttf</file>
+            <file>Roboto-BlackItalic.ttf</file>
         </fileset>
     </family>
 
diff --git a/data/keyboards/Vendor_2378_Product_1008.kl b/data/keyboards/Vendor_2378_Product_1008.kl
new file mode 100644
index 0000000..478da03
--- /dev/null
+++ b/data/keyboards/Vendor_2378_Product_1008.kl
@@ -0,0 +1,35 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# OnLive, Inc. OnLive Wireless Controller, USB adapter
+
+key 304 BUTTON_A
+key 305 BUTTON_B
+key 307 BUTTON_X
+key 308 BUTTON_Y
+key 310 BUTTON_L1
+key 311 BUTTON_R1
+key 315 BUTTON_START
+key 314 BUTTON_SELECT
+key 317 BUTTON_THUMBL
+key 318 BUTTON_THUMBR
+
+axis 0x00 X
+axis 0x01 Y
+axis 0x03 Z
+axis 0x04 RZ
+axis 0x05 RTRIGGER
+axis 0x02 LTRIGGER
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
diff --git a/docs/html/about/dashboards/index.jd b/docs/html/about/dashboards/index.jd
index c61a94b..32b9c9e 100644
--- a/docs/html/about/dashboards/index.jd
+++ b/docs/html/about/dashboards/index.jd
@@ -64,7 +64,7 @@
 </div>
 
 
-<p style="clear:both"><em>Data collected during a 7-day period ending on May 1, 2014.
+<p style="clear:both"><em>Data collected during a 7-day period ending on June 4, 2014.
 <br/>Any versions with less than 0.1% distribution are not shown.</em>
 </p>
 
@@ -95,7 +95,7 @@
 </div>
 
 
-<p style="clear:both"><em>Data collected during a 7-day period ending on May 1, 2014.
+<p style="clear:both"><em>Data collected during a 7-day period ending on June 4, 2014.
 <br/>Any screen configurations with less than 0.1% distribution are not shown.</em></p>
 
 
@@ -114,7 +114,7 @@
 
 
 <img alt="" style="float:right"
-src="//chart.googleapis.com/chart?chs=400x250&cht=p&chd=t%3A0.1%2C87.0%2C12.9&chf=bg%2Cs%2C00000000&chl=GL%201.1%20only%7CGL%202.0%7CGL%203.0&chco=c4df9b%2C6fad0c" />
+src="//chart.googleapis.com/chart?chs=400x250&cht=p&chd=t%3A0.1%2C83.6%2C16.3&chf=bg%2Cs%2C00000000&chl=GL%201.1%20only%7CGL%202.0%7CGL%203.0&chco=c4df9b%2C6fad0c" />
 
 <p>To declare which version of OpenGL ES your application requires, you should use the {@code
 android:glEsVersion} attribute of the <a
@@ -136,17 +136,17 @@
 </tr>
 <tr>
 <td>2.0</th>
-<td>87.0%</td>
+<td>83.6%</td>
 </tr>
 <tr>
 <td>3.0</th>
-<td>12.9%</td>
+<td>16.3%</td>
 </tr>
 </table>
 
 
 
-<p style="clear:both"><em>Data collected during a 7-day period ending on May 1, 2014</em></p>
+<p style="clear:both"><em>Data collected during a 7-day period ending on June 4, 2014</em></p>
 
 
 
@@ -164,47 +164,42 @@
 var VERSION_DATA =
 [
   {
-    "chart": "//chart.googleapis.com/chart?chs=500x250&cht=p&chd=t%3A1.0%2C16.2%2C0.1%2C13.4%2C60.8%2C8.5&chf=bg%2Cs%2C00000000&chl=Froyo%7CGingerbread%7CHoneycomb%7CIce%20Cream%20Sandwich%7CJelly%20Bean%7CKitKat&chco=c4df9b%2C6fad0c",
+    "chart": "//chart.googleapis.com/chart?chl=Froyo%7CGingerbread%7CIce%20Cream%20Sandwich%7CJelly%20Bean%7CKitKat&chf=bg%2Cs%2C00000000&chd=t%3A0.8%2C14.9%2C12.3%2C58.4%2C13.6&chco=c4df9b%2C6fad0c&cht=p&chs=500x250",
     "data": [
       {
         "api": 8,
         "name": "Froyo",
-        "perc": "1.0"
+        "perc": "0.8"
       },
       {
         "api": 10,
         "name": "Gingerbread",
-        "perc": "16.2"
-      },
-      {
-        "api": 13,
-        "name": "Honeycomb",
-        "perc": "0.1"
+        "perc": "14.9"
       },
       {
         "api": 15,
         "name": "Ice Cream Sandwich",
-        "perc": "13.4"
+        "perc": "12.3"
       },
       {
         "api": 16,
         "name": "Jelly Bean",
-        "perc": "33.5"
+        "perc": "29.0"
       },
       {
         "api": 17,
         "name": "Jelly Bean",
-        "perc": "18.8"
+        "perc": "19.1"
       },
       {
         "api": 18,
         "name": "Jelly Bean",
-        "perc": "8.5"
+        "perc": "10.3"
       },
       {
         "api": 19,
         "name": "KitKat",
-        "perc": "8.5"
+        "perc": "13.6"
       }
     ]
   }
@@ -226,23 +221,22 @@
         "xhdpi": "0.6"
       },
       "Normal": {
-        "hdpi": "33.9",
-        "mdpi": "12.5",
-        "xhdpi": "19.9",
-        "xxhdpi": "13.5"
+        "hdpi": "34.2",
+        "mdpi": "12.0",
+        "xhdpi": "19.6",
+        "xxhdpi": "14.6"
       },
       "Small": {
-        "ldpi": "7.5"
+        "ldpi": "7.2"
       },
       "Xlarge": {
         "hdpi": "0.3",
-        "ldpi": "0.1",
-        "mdpi": "4.2",
+        "mdpi": "4.0",
         "xhdpi": "0.3"
       }
     },
-    "densitychart": "//chart.googleapis.com/chart?chs=400x250&cht=p&chd=t%3A8.2%2C21.1%2C1.6%2C34.8%2C20.8%2C13.5&chf=bg%2Cs%2C00000000&chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi&chco=c4df9b%2C6fad0c",
-    "layoutchart": "//chart.googleapis.com/chart?chs=400x250&cht=p&chd=t%3A4.9%2C7.8%2C80.0%2C7.5&chf=bg%2Cs%2C00000000&chl=Xlarge%7CLarge%7CNormal%7CSmall&chco=c4df9b%2C6fad0c"
+    "densitychart": "//chart.googleapis.com/chart?chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi&chf=bg%2Cs%2C00000000&chd=t%3A7.8%2C20.4%2C1.6%2C35.1%2C20.5%2C14.6&chco=c4df9b%2C6fad0c&cht=p&chs=400x250",
+    "layoutchart": "//chart.googleapis.com/chart?chl=Xlarge%7CLarge%7CNormal%7CSmall&chf=bg%2Cs%2C00000000&chd=t%3A4.6%2C7.8%2C80.4%2C7.2&chco=c4df9b%2C6fad0c&cht=p&chs=400x250"
   }
 ];
 
diff --git a/docs/html/community/index.html b/docs/html/community/index.html
index eeb1c51..e3834ba 100644
--- a/docs/html/community/index.html
+++ b/docs/html/community/index.html
@@ -34,6 +34,9 @@
 </script>
 
 <style>
+#header {
+  padding: 2.2em 0 0.2em 0;
+}
 #header-wrap h1 {
   margin:0;
   padding:0;
diff --git a/docs/html/distribute/engage/analytics.jd b/docs/html/distribute/engage/analytics.jd
new file mode 100644
index 0000000..5f7cade
--- /dev/null
+++ b/docs/html/distribute/engage/analytics.jd
@@ -0,0 +1,50 @@
+page.title=Understand User Behavior
+page.metaDescription=Use Google Analytics to learn what your users like and what keeps them coming back.
+page.tags="analytics, user behavior"
+page.image=/images/gp-analytics.jpg
+
+@jd:body
+
+<div class="figure">
+  <img src="{@docRoot}images/gp-analytics.jpg" style="width:320px">
+</div>
+
+
+<p>
+  Link your Google Play Developer Console with Google Analytics to learn much
+  more about how users interact with your app &mdash; before and after they
+  download it.
+</p>
+
+<p>
+  Start by discovering how many people visit your Google Play listing page,
+  where they come from, and how many go on to install your app. Once the app is
+  launched, use Google Analytics to see which of your features are most
+  popular, where power users spend their time, who tends to make in-app
+  purchases, and more.
+</p>
+
+<p>
+  Google Analytics delivers the numbers in real time, so you can act fast to
+  update your landing page and your app. <a href=
+  "http://www.google.com/analytics/mobile/">Learn more</a>.
+</p>
+
+<p>
+  If you have a Google Analytics account already, linking it to Google Play
+  takes just a few moments. You can also link your Google Analytics account to
+  Admob to start gaining more user insights to improve your in-app marketing.
+</p>
+
+  <div class="headerLine clearfloat">
+  <h2 id="related-resources">
+    Related Resources
+  </h2>
+</div>
+
+<div class="resource-widget resource-flow-layout col-13" 
+  data-query="collection:distribute/engage/analytics"
+  data-sortorder="-timestamp"
+  data-cardsizes="9x3"
+  data-maxresults="6">
+</div>
\ No newline at end of file
diff --git a/docs/html/distribute/engage/app-updates.jd b/docs/html/distribute/engage/app-updates.jd
index 6b751b9..2b7cd2c 100644
--- a/docs/html/distribute/engage/app-updates.jd
+++ b/docs/html/distribute/engage/app-updates.jd
@@ -36,12 +36,12 @@
 "18x6," data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="related-resources">
+<div class="headerLine">
+  <h2 id="related-resources">
     Related Resources
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
diff --git a/docs/html/distribute/engage/community.jd b/docs/html/distribute/engage/community.jd
index 035058a..e202d54 100644
--- a/docs/html/distribute/engage/community.jd
+++ b/docs/html/distribute/engage/community.jd
@@ -29,12 +29,14 @@
   Learn more about how to <a href="{@docRoot}distribute/users/build-community.html">build and manage a community</a>.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="related-resources">
+<p style="clear:both">
+</p>
+<div class="headerLine">
+  <h2 id="related-resources">
     Related Resources
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
diff --git a/docs/html/distribute/engage/deep-linking.jd b/docs/html/distribute/engage/deep-linking.jd
index cd62f9d..a25c3c6 100644
--- a/docs/html/distribute/engage/deep-linking.jd
+++ b/docs/html/distribute/engage/deep-linking.jd
@@ -1,17 +1,17 @@
 page.title=Deep Link to Bring Users Back
-page.metaDescription=Use deep links to bring your users into your apps from social posts or search.
+page.metaDescription=Use deep links to bring your users into your apps from social posts, search, or ads.
 page.tags="app indexing, google+ signin"
 page.image=/images/gp-listing-4.jpg
 
 @jd:body
 
 <p>
-  Use deep links to bring your users into your apps from social posts or
-  search.
+  Use deep links to bring your users into your apps from social posts,
+  search, or ads.
 </p>
 
 <div class="headerLine">
-<h1>Deep Linking from Google+ Posts</h1><hr>
+<h2>Deep Linking from Google+ Posts</h2>
 </div>
 
 <p>
@@ -43,8 +43,13 @@
 </div>
 
 
-<div class="headerLine clearfloat">
-<h1>Deep Linking from Google Search &mdash; App Indexing</h1><hr>
+<div class="headerLine">
+<h2>Deep Linking from Google Search &mdash; App Indexing</h2>
+</div>
+
+
+<div style="float:right;">
+  <img src="/images/gp-listing-4.jpg" style="padding-top:1em;padding-left:2em;">
 </div>
 
 <p>
@@ -60,16 +65,32 @@
   content</a>.
 </p>
 
-<div>
-  <img src="{@docRoot}images/gp-listing-4.jpg" style="padding-top:1em;">
+<div class="clearfloat" style="margin-top:2em;"></div>
+
+<div style="float:right;width:340px;padding-left:2em;">
+  <img src="/images/gp-ads-linking2.jpg" style="padding-top:1em;">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="related-resources">
-    Related Resources
-  </h1>
+<div class="headerLine ">
+<h2>Deep Linking from Google Ads</h2>
+</div>
+<p>
+  Ads can remind users about the apps they already have.
+</p>
 
-  <hr>
+<p>
+  As with deep links from Google's organic search results, AdWords deep links
+  send users directly to the relevant pages in apps they already have on their
+  mobile device. A mobile search for "flights to London," for instance, could
+  take a user straight to the London page in a travel app. <a href=
+  "http://www.thinkwithgoogle.com/products/ads-apps.html"
+  class="external-link">Learn more</a>.
+</p>
+
+<div class="headerLine clearfloat">
+  <h2 id="related-resources">
+    Related Resources
+  </h2>
 </div>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
diff --git a/docs/html/distribute/engage/easy-signin.jd b/docs/html/distribute/engage/easy-signin.jd
index 92c3ffc..d066181 100644
--- a/docs/html/distribute/engage/easy-signin.jd
+++ b/docs/html/distribute/engage/easy-signin.jd
@@ -37,11 +37,11 @@
 </p>
 
 <div class="headerLine">
-  <h1>
+  <h2>
     And Spreading the Word a Snap
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 
@@ -85,12 +85,14 @@
   </li>
 </ul>
 
-  <div class="headerLine clearfloat">
-    <h1 id="related-resources">
+<p style="clear:both">
+</p>
+  <div class="headerLine">
+    <h2 id="related-resources">
       Related Resources
-    </h1>
+    </h2>
 
-    <hr>
+
   </div>
 
   <div class="resource-widget resource-flow-layout col-13"
diff --git a/docs/html/distribute/engage/engage_toc.cs b/docs/html/distribute/engage/engage_toc.cs
index 0314f8c..596051a 100644
--- a/docs/html/distribute/engage/engage_toc.cs
+++ b/docs/html/distribute/engage/engage_toc.cs
@@ -37,6 +37,12 @@
   </li>
   <li class="nav-section">
     <div class="nav-section empty" style="font-weight:normal"><a href="<?cs
+        var:toroot?>distribute/engage/analytics.html">
+        <span class="en">Understand User Behavior</span></a>
+    </div>
+  </li>
+  <li class="nav-section">
+    <div class="nav-section empty" style="font-weight:normal"><a href="<?cs
         var:toroot?>distribute/engage/app-updates.html">
         <span class="en">Update Regularly</span></a>
     </div>
diff --git a/docs/html/distribute/engage/game-services.jd b/docs/html/distribute/engage/game-services.jd
index 5153435..1c77d2d 100644
--- a/docs/html/distribute/engage/game-services.jd
+++ b/docs/html/distribute/engage/game-services.jd
@@ -75,12 +75,12 @@
   Game Developer Best Practices</a>.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="related-resources">
+<div class="headerLine">
+  <h2 id="related-resources">
     Related Resources
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
diff --git a/docs/html/distribute/engage/gcm.jd b/docs/html/distribute/engage/gcm.jd
index d793124e..7d9b6bb 100644
--- a/docs/html/distribute/engage/gcm.jd
+++ b/docs/html/distribute/engage/gcm.jd
@@ -35,12 +35,12 @@
   free and there are no quotas.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="related-resources">
+<div class="headerLine">
+  <h2 id="related-resources">
     Related Resources
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="resource-widget resource-flow-layout col-13" 
diff --git a/docs/html/distribute/engage/index.jd b/docs/html/distribute/engage/index.jd
index f8cd1ee..2b103c3 100644
--- a/docs/html/distribute/engage/index.jd
+++ b/docs/html/distribute/engage/index.jd
@@ -15,8 +15,8 @@
 
   <div class="resource-widget resource-flow-layout landing col-16"
     data-query="collection:distribute/engagelanding"
-    data-cardSizes="6x6"
-    data-maxResults="9">
+    data-cardSizes="9x6,9x6,6x6,6x6,6x6,9x6,9x6,6x6,6x6,6x6"
+    data-maxResults="10">
   </div>
 
   <h3>Related Resources</h3>
diff --git a/docs/html/distribute/engage/notifications.jd b/docs/html/distribute/engage/notifications.jd
index fecfb45..1aa0637 100644
--- a/docs/html/distribute/engage/notifications.jd
+++ b/docs/html/distribute/engage/notifications.jd
@@ -40,17 +40,15 @@
 </p>
 
 
-  <div class="sidebox" style="width:326px;float:left;margin-left:0">
           <p><strong>Tip:</strong>
        Use notifications sparingly &mdash; be sure any information presented is
       useful. Give users the option to turn notifications off.
     </p>
-  </div>
 
-  <div class="headerLine clearfloat">
-  <h1 id="related-resources">
+  <div class="headerLine">
+  <h2 id="related-resources">
     Related Resources
-  </h1><hr>
+  </h2>
 </div>
 
 <div class="resource-widget resource-flow-layout col-13" 
diff --git a/docs/html/distribute/engage/video.jd b/docs/html/distribute/engage/video.jd
index 1a30f3a..c5a4997 100644
--- a/docs/html/distribute/engage/video.jd
+++ b/docs/html/distribute/engage/video.jd
@@ -23,12 +23,12 @@
   <img src="{@docRoot}images/gp-engage-smule.jpg">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="related-resources">
+<div class="headerLine">
+  <h2 id="related-resources">
     Related Resources
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
diff --git a/docs/html/distribute/engage/widgets.jd b/docs/html/distribute/engage/widgets.jd
index b17af08..6adb55c 100644
--- a/docs/html/distribute/engage/widgets.jd
+++ b/docs/html/distribute/engage/widgets.jd
@@ -1,5 +1,5 @@
 page.title=Build Useful Widgets
-page.metaDescription=Use widgets to remind users about important information in your apps and games, even when your apps are closed.
+page.metaDescription=Use home screen widgets to remind users about important information in your apps and games, even when your apps are closed.
 page.tags=""
 page.image=/images/gp-engage-0.jpg
 
@@ -28,10 +28,13 @@
   or upcoming deadlines. Widgets should serve as more than a launcher icon.</p>
   </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="related-resources">
+<p style="clear:both">
+</p>
+
+<div class="headerLine">
+  <h2 id="related-resources">
     Related Resources
-  </h1><hr>
+  </h2>
 </div>
 
 <div class="resource-widget resource-flow-layout col-13" 
diff --git a/docs/html/distribute/essentials/best-practices/apps.jd b/docs/html/distribute/essentials/best-practices/apps.jd
index 055a349..bbac727 100644
--- a/docs/html/distribute/essentials/best-practices/apps.jd
+++ b/docs/html/distribute/essentials/best-practices/apps.jd
@@ -17,7 +17,7 @@
 <p>The following best practices have enabled developers worldwide to build great, successful apps for Google Play.</p>
 
 <div class="headerLine">
-<h1 id="essentials">Get the Essentials Right</h1><hr>
+<h2 id="essentials">Get the Essentials Right</h2>
 </div>
 
 <h3>1. Make it Android</h3>
@@ -82,11 +82,11 @@
 </ul>
 
 <div class="headerLine">
-  <h1 id="users">
+  <h2 id="users">
   Get Users
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <h3>
@@ -150,11 +150,11 @@
 </ul>
 
 <div class="headerLine">
-  <h1 id="engage">
+  <h2 id="engage">
   Engage and Retain
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <h3>
@@ -211,11 +211,11 @@
 </ul>
 
 <div class="headerLine">
-  <h1 id="beyond">
+  <h2 id="beyond">
   Beyond the Basics
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <ul>
@@ -249,7 +249,7 @@
 </ul>
 
 <div class="headerLine">
-<h1 id="related-resources">Related Resources</h1><hr>
+<h2 id="related-resources">Related Resources</h2>
 </div>
 
 <div class="resource-widget resource-flow-layout col-13"
diff --git a/docs/html/distribute/essentials/best-practices/games.jd b/docs/html/distribute/essentials/best-practices/games.jd
index ac1df44..c4ce66e 100644
--- a/docs/html/distribute/essentials/best-practices/games.jd
+++ b/docs/html/distribute/essentials/best-practices/games.jd
@@ -20,11 +20,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="users">
+  <h2 id="users">
   Get Users
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <h3>
@@ -111,11 +111,11 @@
 </ul>
 
 <div class="headerLine">
-  <h1 id="engage">
+  <h2 id="engage">
   Engage and Retain
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <h3>
@@ -213,11 +213,11 @@
 </ul>
 
 <div class="headerLine">
-  <h1 id="beyond">
+  <h2 id="beyond">
   Beyond the Basics
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <ul>
@@ -249,7 +249,7 @@
 </ul>
 
 <div class="headerLine">
-<h1 id="related-resources">Related Resources</h1><hr>
+<h2 id="related-resources">Related Resources</h2>
 </div>
 
 <div class="resource-widget resource-flow-layout col-13"
diff --git a/docs/html/distribute/essentials/gpfe-guidelines.jd b/docs/html/distribute/essentials/gpfe-guidelines.jd
index 799009f..734bddc 100644
--- a/docs/html/distribute/essentials/gpfe-guidelines.jd
+++ b/docs/html/distribute/essentials/gpfe-guidelines.jd
@@ -50,12 +50,12 @@
   Distribution Agreement</a>.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="basic-reqts">
+<div class="headerLine">
+  <h2 id="basic-reqts">
     Basic Requirements
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -108,11 +108,11 @@
 </ul>
 
 <div class="headerLine">
-  <h1 id="monetizing-ads">
+  <h2 id="monetizing-ads">
     Monetizing and Ads
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -195,11 +195,11 @@
 </ul>
 
 <div class="headerLine">
-  <h1 id="e-value">
+  <h2 id="e-value">
     Educational Value
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure">
@@ -299,11 +299,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="quality">
+  <h2 id="quality">
     App Quality
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure">
@@ -410,11 +410,11 @@
 </ul>
 
 <div class="headerLine">
-  <h1 id="test-environment">
+  <h2 id="test-environment">
     Test Environment
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -487,7 +487,7 @@
 </ul>
 
 <div class="headerLine">
-<h1>Related Resources</h1><hr>
+<h2>Related Resources</h2>
 </div>
 
 <div class="dynamic-grid">
diff --git a/docs/html/distribute/essentials/optimizing-your-app.jd b/docs/html/distribute/essentials/optimizing-your-app.jd
index 3fe91b28..696ef53 100644
--- a/docs/html/distribute/essentials/optimizing-your-app.jd
+++ b/docs/html/distribute/essentials/optimizing-your-app.jd
@@ -53,11 +53,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="listen-to-your-users">
+  <h2 id="listen-to-your-users">
     Listen to Your Users
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure">
@@ -147,11 +147,11 @@
 </p>
 
 <div class="headerLine" id="measuring-analyzing-responding">
-  <h1>
+  <h2>
     Measuring, Analyzing, and Responding to User Behavior
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure">
@@ -260,11 +260,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="improve-stability">
+  <h2 id="improve-stability">
     Improve Stability and Eliminate Bugs
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -298,11 +298,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="improve-ui">
+  <h2 id="improve-ui">
     Improve UI Responsiveness
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure">
@@ -352,11 +352,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="improve-usability">
+  <h2 id="improve-usability">
     Improve Usability
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="sidebox-wrapper" style="float:right;">
@@ -403,11 +403,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="professional-appearance">
+  <h2 id="professional-appearance">
     Professional Appearance and Aesthetics
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -439,11 +439,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="deliver-features">
+  <h2 id="deliver-features">
     Deliver the Right Set of Features
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -464,11 +464,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="integrate">
+  <h2 id="integrate">
     Integrate with the System and Third-Party apps
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -502,7 +502,7 @@
 </p>
 
 <div class="headerLine">
-<h1 id="related-resources">Related Resources</h1><hr>
+<h2 id="related-resources">Related Resources</h2>
 </div>
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/essentials/optimizing, tag:addia"
diff --git a/docs/html/distribute/essentials/quality/core.jd b/docs/html/distribute/essentials/quality/core.jd
index 558b030..cfe1a2a 100644
--- a/docs/html/distribute/essentials/quality/core.jd
+++ b/docs/html/distribute/essentials/quality/core.jd
@@ -63,12 +63,12 @@
   Guidelines</a>.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="ux">
+<div class="headerLine">
+  <h2 id="ux">
   Visual Design and User Interaction
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -234,9 +234,7 @@
   </tr>
 </table>
 
-<h3>
-  Related Resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/essentials/corequalityguidelines/visualdesign"
@@ -244,12 +242,12 @@
 data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="fn">
+<div class="headerLine">
+  <h2 id="fn">
   Functionality
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -509,21 +507,19 @@
   </tr>
 </table>
 
-<h3>
-  Related Resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/essentials/corequalityguidelines/functionality"
 data-sortorder="-timestamp" data-cardsizes="6x3" data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="ps">
+<div class="headerLine">
+  <h2 id="ps">
   Performance and Stability
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -670,21 +666,19 @@
   </tr>
 </table>
 
-<h3>
-  Related Resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/essentials/core/performance" data-sortorder="-timestamp"
 data-cardsizes="6x3" data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="listing">
+<div class="headerLine">
+  <h2 id="listing">
   Google Play
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -819,21 +813,19 @@
   </tr>
 </table>
 
-<h3>
-  Related Resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/essentials/core/play" data-sortorder="-timestamp"
 data-cardsizes="6x3,6x3,6x3,6x3,6x3,6x3" data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="test-environment">
+<div class="headerLine">
+  <h2 id="test-environment">
   Setting Up a Test Environment
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -863,12 +855,12 @@
   increase the number or complexity of tests and quality criteria.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="tests">
+<div class="headerLine">
+  <h2 id="tests">
   Test Procedures
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
diff --git a/docs/html/distribute/essentials/quality/tablets.jd b/docs/html/distribute/essentials/quality/tablets.jd
index 7dfab48..2b2a5ae 100644
--- a/docs/html/distribute/essentials/quality/tablets.jd
+++ b/docs/html/distribute/essentials/quality/tablets.jd
@@ -52,7 +52,7 @@
   help you address each recommendation included.
 </p>
 
-<div class="headerLine"><h1 id="core-app-quality">1. Test for Basic Tablet App Quality</h1><hr></div>
+<div class="headerLine"><h2 id="core-app-quality">1. Test for Basic Tablet App Quality</h2></div>
 
 <p>The first step in delivering a great tablet app experience is making sure
 that it meets the <em>core app quality criteria</em> for all of the devices
@@ -78,8 +78,8 @@
   Tips page</a>.</p>
 
 
-<div class="headerLine clearfloat">
-<h1 id="optimize-layouts">2. Optimize Layouts for Larger Screens</h1><hr></div>
+<div class="headerLine">
+<h2 id="optimize-layouts">2. Optimize Layouts for Larger Screens</h2></div>
 
 <p>
   Android makes it easy to develop an app that runs well on a wide range of
@@ -158,7 +158,7 @@
 multi-pane UI for tablets (see next section).</li>
 </ul>
 
-<h3>Related resources</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/essentials/tabletguidelines/optimize"
@@ -167,7 +167,7 @@
   data-maxResults="6"></div>
 
 
-<div class="headerLine clearfloat"><h1 id="use-extra-space">3. Take Advantage of Extra Screen Area</h1><hr></div>
+<div class="headerLine"><h2 id="use-extra-space">3. Take Advantage of Extra Screen Area</h2></div>
 
 <div style="width:340px;float:right;margin:1.5em;margin-bottom:0;margin-top:0;">
 <img src="{@docRoot}images/training/app-navigation-multiple-sizes-multipane-good.png"
@@ -219,7 +219,7 @@
 <code>sw600dp</code>/<code>sw720</code>).</li>
 </ul>
 
-<h3>Related resources</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/essentials/tabletguidelines/extrascreen"
@@ -227,7 +227,7 @@
   data-cardSizes="6x3,6x3,6x3"
   data-maxResults="6"></div>
 
-<div class="headerLine clearfloat"><h1 id="use-tablet-icons">4. Use Assets Designed for Tablet Screens</h1><hr></div>
+<div class="headerLine"><h2 id="use-tablet-icons">4. Use Assets Designed for Tablet Screens</h2></div>
 
 <div><img src="{@docRoot}design/media/devices_displays_density@2x.png"></div>
 
@@ -308,7 +308,7 @@
 it will request the {@code xxhdpi} version of the launcher icon.</li>
 </ul>
 
-<h3>Related resources</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/essentials/tabletguidelines/assets"
@@ -316,8 +316,8 @@
   data-cardSizes="9x3"
   data-maxResults="6"></div>
 
-<div class="headerLine clearfloat"><h1 id="adjust-font-sizes">5.
-Adjust Font Sizes and Touch Targets</h1><hr></div>
+<div class="headerLine"><h2 id="adjust-font-sizes">5.
+Adjust Font Sizes and Touch Targets</h2></div>
 
 <p>To make sure your app is easy to use on tablets, take some time to adjust the
 font sizes and touch targets in your tablet UI, for all of the screen
@@ -345,7 +345,7 @@
 or just centering the icon within the transparent button.</li>
 </ul>
 
-<h3>Related resources</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/essentials/tabletguidelines/fonts"
@@ -353,7 +353,7 @@
   data-cardSizes="9x3,9x3,6x3,6x3,6x3"
   data-maxResults="6"></div>
 
-<div class="headerLine clearfloat"><h1 id="adjust-widgets">6. Adjust Sizes of Home Screen Widgets</h1><hr></div>
+<div class="headerLine"><h2 id="adjust-widgets">6. Adjust Sizes of Home Screen Widgets</h2></div>
 
 <p>If your app includes a home screen widget, here are a few points to consider
 to ensure a great user experience on tablet screens: </p>
@@ -371,7 +371,7 @@
 possible.</li>
 </ul>
 
-<h3>Related resources</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/essentials/tabletguidelines/widgets"
@@ -380,7 +380,7 @@
   data-maxResults="6"></div>
 
 
-<div class="headerLine clearfloat"><h1 id="offer-full-feature-set">7. Full Feature Set for Tablet Users</h1><hr></div>
+<div class="headerLine"><h2 id="offer-full-feature-set">7. Full Feature Set for Tablet Users</h2></div>
 
 <div class="centered-full-image" style="width:600px;margin:1.5em"><img src="{@docRoot}images/gp-tablets-full-feature-set.png" alt="Tablet feature sets"></div>
 
@@ -415,7 +415,7 @@
   </li>
 </ul>
 
-<div class="headerLine clearfloat"><h1 id="android-versions">8. Target Android Versions Properly</h1><hr></div>
+<div class="headerLine"><h2 id="android-versions">8. Target Android Versions Properly</h2></div>
 
 <p>
   To ensure the broadest possible distribution to tablets, make sure that your
@@ -458,7 +458,7 @@
   </li>
 </ol>
 
-<h3>Related resources</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/essentials/tabletguidelines/versions"
@@ -466,7 +466,7 @@
   data-cardSizes="6x3"
   data-maxResults="6"></div>
 
-<div class="headerLine clearfloat"><h1 id="hardware-requirements">9. Declare Hardware Feature Dependencies Properly</h1><hr></div>
+<div class="headerLine"><h2 id="hardware-requirements">9. Declare Hardware Feature Dependencies Properly</h2></div>
 
 <p>
   Handsets and tablets typically offer slightly different hardware support for
@@ -528,7 +528,7 @@
   as needed.
 </p>
 
-<h3>Related resources</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/essentials/tabletguidelines/hardware"
@@ -536,7 +536,7 @@
   data-cardSizes="9x3"
   data-maxResults="6"></div>
 
-<div class="headerLine clearfloat"><h1 id="support-screens">10. Declare Support for Tablet Screens</h1><hr></div>
+<div class="headerLine"><h2 id="support-screens">10. Declare Support for Tablet Screens</h2></div>
 
 <p>To ensure that you can distribute your app to a broad range of tablets, your app should
 declare support for tablet screen sizes in its manifest file, as follows:</p>
@@ -560,7 +560,7 @@
 <a href="{@docRoot}guide/topics/manifest/compatible-screens-element.html"><code>&lt;compatible-screens&gt;</code></a>
 element in your app.</p>
 
-<h3>Related resources</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/essentials/tabletguidelines/tabletscreens"
@@ -569,7 +569,7 @@
   data-maxResults="6"></div>
 
 
-<div class="headerLine clearfloat"><h1 id="google-play">11. Showcase Your Tablet UI in Google Play</h1><hr></div>
+<div class="headerLine"><h2 id="google-play">11. Showcase Your Tablet UI in Google Play</h2></div>
 
 <p>
   After you've done the work to create an rich, optimized UI for your tablet
@@ -689,7 +689,7 @@
   </li>
 </ul>
 
-<h3>Related resources</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/essentials/tabletguidelines/showcase"
@@ -697,12 +697,12 @@
   data-cardSizes="9x3,9x3,9x3,9x3"
   data-maxResults="6"></div>
 
-<div class="headerLine clearfloat">
-  <h1 id="google-play-best-practices">
+<div class="headerLine">
+  <h2 id="google-play-best-practices">
     12. Follow Best Practices for Publishing in Google Play
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -799,7 +799,7 @@
   recommended.
 </p>
 
-<h3 class="clearfloat">Related resources</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/essentials/tabletguidelines/googleplay"
   data-sortOrder="-timestamp"
@@ -807,12 +807,12 @@
   data-maxResults="6"></div>
 
 
-<div class="headerLine clearfloat">
-  <h1 id="test-environment">
+<div class="headerLine">
+  <h2 id="test-environment">
     Setting Up a Test Environment for Tablets
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -858,7 +858,7 @@
 </tr>
 </table>
 
-<div class="headerLine clearfloat"><h1 id="related-resources">Related Resources</h1><hr></div>
+<div class="headerLine"><h2 id="related-resources">Related Resources</h2></div>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/essentials/tabletguidelines"
diff --git a/docs/html/distribute/googleplay/about.jd b/docs/html/distribute/googleplay/about.jd
index cf0c6d2..c7c91ac 100644
--- a/docs/html/distribute/googleplay/about.jd
+++ b/docs/html/distribute/googleplay/about.jd
@@ -58,11 +58,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="ratings-reviews">
+  <h2 id="ratings-reviews">
     User Ratings and Reviews
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -83,11 +83,11 @@
 </div>
 
 <div class="headerLine">
-  <h1 id="category-browsing">
+  <h2 id="category-browsing">
     Category Browsing
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -98,11 +98,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="search">
+  <h2 id="search">
     Search
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -113,11 +113,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="top-charts-and-lists">
+  <h2 id="top-charts-and-lists">
     Top Charts and Lists
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure">
@@ -195,11 +195,11 @@
 </table>
 
 <div class="headerLine">
-  <h1 id="featured-staff-picks">
+  <h2 id="featured-staff-picks">
     Featured, Staff Picks, Collections, and Badges
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -313,11 +313,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="product-detail-pages">
+  <h2 id="product-detail-pages">
     Store Listing Pages
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure">
@@ -357,8 +357,11 @@
   Products</a> to find out how.
 </p>
 
-<div class="headerLine clearfloat">
-<h1>Related Resources</h1><hr>
+<p style="clear:both">
+</p>
+
+<div class="headerLine">
+<h2>Related Resources</h2>
 </div>
 
 <div class="resource-widget resource-flow-layout col-13"
diff --git a/docs/html/distribute/googleplay/developer-console.jd b/docs/html/distribute/googleplay/developer-console.jd
index 6263431..f5b3ac6 100644
--- a/docs/html/distribute/googleplay/developer-console.jd
+++ b/docs/html/distribute/googleplay/developer-console.jd
@@ -44,12 +44,12 @@
   verification by email, you can sign in to your Google Play Developer Console.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="allapps">
+<div class="headerLine">
+  <h2 id="allapps">
     All Applications
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -61,12 +61,12 @@
   <img src="{@docRoot}images/gp-dc-home.png" class="border-img">
 </div>
 
-<div class="headerLine clearfloat" style="margin-top:-6px">
-  <h1 id="account-details">
+<div class="headerLine" style="margin-top:-6px">
+  <h2 id="account-details">
     Your Account Details
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -112,12 +112,12 @@
   Google Play licensing.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="merchant-account">
+<div class="headerLine">
+  <h2 id="merchant-account">
     Linking Your Merchant Account
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -127,12 +127,12 @@
   from sales.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="multiple-user-accounts">
+<div class="headerLine">
+  <h2 id="multiple-user-accounts">
     Multiple User Accounts
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -157,12 +157,12 @@
   up multiple accounts</a> now.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="store-listing-details">
+<div class="headerLine">
+  <h2 id="store-listing-details">
     Store Listing Details
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -181,12 +181,12 @@
   <img src="{@docRoot}images/gp-dc-details.png" class="frame">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="upload-instantly-publish">
+<div class="headerLine">
+  <h2 id="upload-instantly-publish">
     Upload and Instantly Publish
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -212,12 +212,12 @@
   time.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="alpha-beta">
+<div class="headerLine">
+  <h2 id="alpha-beta">
     Alpha and Beta Testing
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -270,12 +270,12 @@
   Checklist</a>.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="staged-rollouts">
+<div class="headerLine">
+  <h2 id="staged-rollouts">
     Staged Rollouts
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -303,12 +303,12 @@
   updates.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="multiple-apk">
+<div class="headerLine">
+  <h2 id="multiple-apk">
     Multiple APK Support
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -335,12 +335,12 @@
   of the normal app installation.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="selling-pricing-your-products">
+<div class="headerLine">
+  <h2 id="selling-pricing-your-products">
     Selling and Pricing Your Products
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure-right">
@@ -407,12 +407,12 @@
   Expand into New Markets</a>.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="in-app-products">
+<div class="headerLine">
+  <h2 id="in-app-products">
     In-app Products
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -448,12 +448,12 @@
   monetization models
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="distribution-controls">
+<div class="headerLine">
+  <h2 id="distribution-controls">
     Distribution Controls
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -518,12 +518,12 @@
   exclude specific devices if needed.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="reviews-reports">
+<div class="headerLine">
+  <h2 id="reviews-reports">
     User Reviews and Crash Reports
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure-right" style="width:500px;">
@@ -548,12 +548,12 @@
   devices.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="app-stats">
+<div class="headerLine">
+  <h2 id="app-stats">
     App Statistics
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure" style="width:500px">
@@ -587,9 +587,12 @@
   on data inside a dimension by adding specific points to the timeline.
 </p>
 
+<p style="clear:both">
+</p>
+
 <div class="dynamic-grid">
-<div class="headerLine clearfloat">
-<h1 id="related-resources">Related Resources</h1><hr/>
+<div class="headerLine">
+<h2 id="related-resources">Related Resources</h2>
 </div>
 
 <div class="resource-widget resource-flow-layout col-13"
diff --git a/docs/html/distribute/googleplay/edu/about.jd b/docs/html/distribute/googleplay/edu/about.jd
index 3944909..e73356e 100644
--- a/docs/html/distribute/googleplay/edu/about.jd
+++ b/docs/html/distribute/googleplay/edu/about.jd
@@ -106,8 +106,10 @@
   </div>
 </div>
 
-<div class="headerLine clearfloat">
-<h1>Related Resources</h1><hr>
+<p style="clear:both">
+</p>
+<div class="headerLine">
+<h2 id="related-resources">Related Resources</h2>
 </div>
 
 <div class="dynamic-grid">
diff --git a/docs/html/distribute/googleplay/edu/faq.jd b/docs/html/distribute/googleplay/edu/faq.jd
index 0866da5..36e2064 100644
--- a/docs/html/distribute/googleplay/edu/faq.jd
+++ b/docs/html/distribute/googleplay/edu/faq.jd
@@ -58,11 +58,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="business-model-and-monetization">
+  <h2 id="business-model-and-monetization">
   Business Model and Monetization
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -143,11 +143,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="free-trials">
+  <h2 id="free-trials">
   Free Trials
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -172,11 +172,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="discovery">
+  <h2 id="discovery">
   Discovery
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -211,11 +211,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="app-review-process">
+  <h2 id="app-review-process">
   App Review Process
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -255,11 +255,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="app-features">
+  <h2 id="app-features">
   App Features
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -310,11 +310,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="marketing-and-roi">
+  <h2 id="marketing-and-roi">
   Marketing and ROI
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -364,11 +364,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="devices">
+  <h2 id="devices">
   Devices
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -393,11 +393,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="accounts">
+  <h2 id="accounts">
   Accounts
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -423,7 +423,7 @@
   schools in the program use Google Accounts and Google Apps for Education,
   offering Google Single Sign-on is ideal.
 </p>
-<div class="headerLine"><h1 id="related-resources">Related Resources</h1><hr></div>
+<div class="headerLine"><h2 id="related-resources">Related Resources</h2></div>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/toolsreference/gpfefaq"
diff --git a/docs/html/distribute/googleplay/edu/start.jd b/docs/html/distribute/googleplay/edu/start.jd
index 260ae85..4886b5a 100644
--- a/docs/html/distribute/googleplay/edu/start.jd
+++ b/docs/html/distribute/googleplay/edu/start.jd
@@ -32,12 +32,12 @@
   "border:1px solid #ddd;padding:0px;width:100%;">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="register">
+<div class="headerLine">
+  <h2 id="register">
     Register for a Publisher Account
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -49,11 +49,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="prepare">
+  <h2 id="prepare">
     Prepare Your Apps
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure-right">
@@ -138,11 +138,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="publish">
+  <h2 id="publish">
     Publish Your Apps
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -298,7 +298,7 @@
   </li>
 </ul>
 <div class="headerLine">
-<h1 id="related-resources">Related Resources</h1><hr>
+<h2 id="related-resources">Related Resources</h2>
 </div>
 
 <div class="dynamic-grid">
diff --git a/docs/html/distribute/googleplay/start.jd b/docs/html/distribute/googleplay/start.jd
index 6dc397b..2ba5880 100644
--- a/docs/html/distribute/googleplay/start.jd
+++ b/docs/html/distribute/googleplay/start.jd
@@ -33,11 +33,11 @@
 </p>
 
 <div class="headerLine">
-  <h1>
+  <h2>
     Register for a Publisher Account
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="sidebox-wrapper" style="float:right;">
@@ -92,11 +92,11 @@
 </ol>
 
 <div class="headerLine">
-  <h1 id="merchant-account">
+  <h2 id="merchant-account">
     Set Up a Google Wallet Merchant Account
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure" style="width:200px;">
@@ -135,11 +135,11 @@
 </p>
 
 <div class="headerLine">
-  <h1>
+  <h2>
     Explore the Developer Console
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -153,7 +153,7 @@
 </div>
 
 <div class="headerLine">
-<h1 id="related-resources">Related Resources</h1><hr />
+<h2 id="related-resources">Related Resources</h2><hr />
 </div>
 
 <div class="resource-widget resource-flow-layout col-13"
diff --git a/docs/html/distribute/monetize/ads.jd b/docs/html/distribute/monetize/ads.jd
index 40120c3..9a847ff 100644
--- a/docs/html/distribute/monetize/ads.jd
+++ b/docs/html/distribute/monetize/ads.jd
@@ -10,17 +10,32 @@
 </div>
 
 <p>
-  In-app advertising offers a quick and easy way to incorporate a monetization
-  option into both your <a href=
+  Ads can be a quick and easy way to earn more from your <a href=
   "{@docRoot}distribute/monetize/freemium.html">freemium</a>, <a href=
   "{@docRoot}distribute/monetize/premium.html">premium</a>, and <a href=
-  "{@docRoot}distribute/monetize/subscriptions.html">subscription</a> apps. </p>
+  "{@docRoot}distribute/monetize/subscriptions.html">subscription</a> apps.
+  AdMob and the Google Mobile Ads SDK let you add advertising to your apps with
+  just a few lines of code.
+</p>
 
-<p>Using <a href=
+<p>
+  The question is: which model gets the best results for your app? Google's ad
+  tools are made to help you figure out what combination works best for both
+  your audience and your bottom line. </p>
+
+<p>Start by linking your AdMob and Google
+  Analytics accounts to get better insights and more earning power: for
+  instance, AdMob can promote in-app purchases to the people who buy them most
+  often, while showing income-generating ads to those less likely to buy right
+  now.
+</p>
+
+<p>
+  Using <a href=
   "http://www.google.com/ads/admob/monetize.html#subid=us-en-et-dac">AdMob</a>
-  and the <a href="{@docRoot}google/play-services/ads.html">Google
-  Mobile Ads SDK</a> included in Google Play Services, you’re able to add
-  advertising into your apps, with just a few lines of code.
+  and the <a href="{@docRoot}google/play-services/ads.html">Google Mobile Ads
+  SDK</a> included in Google Play Services, you’re able to add advertising into
+  your apps, with just a few lines of code.
 </p>
 
 <p>
@@ -30,36 +45,33 @@
 <ul>
   <li>
     <p>
-      <strong>Placement within your apps</strong> &mdash; Well placed ads will
-      optimize your revenue by making it more likely that users will ‘click
-      through’. Poorly placed ads can result in low click-through rates, and in
-      the worse cases poor rating and users rapidly abandoning your apps. You
-      can get advice on how to best place ads from the developer training on
-      <a href=
-      "{@docRoot}training/monetization/ads-and-ux.html">using
-      ads</a>.
+      <strong>Placement within your apps</strong> &mdash; Well-placed ads make
+      it more likely that users will click through and convert. Poorly-placed
+      ads lead to lower click-through rates, and even poor ratings and users
+      abandoning your apps. Our <a href=
+      "{@docRoot}training/monetization/ads-and-ux.html">developer training</a>
+      on using ads shows some of the best ways to place ads.
     </p>
   </li>
 
   <li>
     <p>
       <strong>Ad formats</strong> &mdash; Every app offers a different type of
-      experience for users, so it’s important to consider the format of ads
-      you’re using to ensure it’s compatible with the experience. While banner
-      ads may work well for a flashlight utility app, an immersive gaming app
-      may benefit more from a video interstitial. Mismatched ad formats may
-      negatively affect your users’ experience and ad revenue, so try to select
-      formats that fit well with the content and flow of your apps.
+      experience for users, so it’s important that your ad formats match that
+      experience. While banner ads may work well for a flashlight utility app,
+      an immersive gaming app may benefit more from a video interstitial.
+      Mismatched ad formats can make users unhappy and leave money on the
+      table.
     </p>
   </li>
 
   <li>
     <p>
-      <strong>Maximizing your performance</strong> &mdash; Ensure you’re optimizing
-      your advertising revenue by maximizing your CPMs <em>and</em> fill rate.
-      Often ad providers will cite very high CPMs but will have a low fill rate
-      that can severely decrease your effective CPM, so look at both of these
-      figures. Also consider using a <a href=
+      <strong>Maximizing your performance</strong> &mdash; Make sure you’re
+      optimizing your advertising revenue by maximizing your CPMs and fill
+      rate. Ad providers often cite their very high CPMs but don't mention low
+      fill rates that can severely decrease your effective CPM. Be sure to look
+      at both of these figures. Consider using a <a href=
       "https://support.google.com/admob/v2/answer/3063564?hl=en&amp;ref_topic=3063091#subid=us-en-et-dac">
       mediation</a> solution if you’d like to use multiple ad providers in your
       apps. Look for solutions that offer yield management or <a href=
@@ -71,30 +83,43 @@
 
   <li>
     <p>
-      <strong>Exercising control options</strong> &mdash; A variety of ads promoting a
-      broad selection of other services or apps may show up within you apps.
-      Depending on your goals and the type of experience you want to provide
-      your users, it may make sense to <a href=
+      <strong>Exercising control options</strong> &mdash; A variety of ads may
+      show up within your app. It may make sense to <a href=
       "https://support.google.com/admob/v2/answer/3150235?hl=enl#subid=us-en-et-dac">
-      block</a> certain advertisements from appearing. Some developers don’t
-      want apps in a similar category showing to their users, but some don’t
-      mind.
+      block</a> certain of those advertisements from appearing, depending on
+      your goals and the type of experience you want to provide. Some
+      developers, for instance, don’t want ads for apps in their same category
+      showing to their users, while others don’t mind at all.
     </p>
   </li>
 
   <li>
     <p>
-      <strong>Cross promoting your other apps</strong> &mdash; Ads can be used for
-      more than just earning revenue. Consider using <a href=
+      <strong>Cross promoting your other apps</strong> &mdash; Ads can do more
+      than earn revenue. Consider running <a href=
       "https://support.google.com/admob/v2/answer/3210452?hl=en#subid=us-en-et-dac">
-      house ads</a> within your apps to create awareness and promote your
-      entire portfolio of apps. When launching new apps, an easy way to quickly
-      attract users is to promote directly to your existing customers.
+      house ads</a> within your apps to promote other apps in your portfolio.
+      When you launch a new app, this kind of promotion is a free and easy way
+      to attract new users quickly.
     </p>
   </li>
 </ul>
 
 <p>
+  Don't forget that paid channels like AdWords and YouTube can help you cast a
+  wider net by reaching targeted audiences outside the app ecosystem. They're a
+  great way to find new users at a price that you control. <a href=
+  "https://support.google.com/adwords/answer/2549053">Learn more</a>.
+</p>
+
+<p>
+  To start monetizing with ads, sign up for AdMob and integrate the Google
+  Mobile Ads SDK into your apps. If you also need to manage direct deals with
+  advertisers, consider using DoubleClick for Publishers Small Business.
+</p>
+
+
+<p>
   To start monetizing with ads sign up for <a href=
   "http://www.google.com/ads/admob/#subid=us-en-et-dac">AdMob</a> and integrate
   the <a href="https://developers.google.com/mobile-ads-sdk/download">Google
@@ -104,7 +129,7 @@
   DoubleClick for Publishers Small Business</a>.
 </p>
 
-<div class="headerLine"><h1 id="related-resources">Related resources</h1><hr></div>
+<div class="headerLine"><h2 id="related-resources">Related resources</h2></div>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/monetize/advertising"
diff --git a/docs/html/distribute/monetize/ecommerce.jd b/docs/html/distribute/monetize/ecommerce.jd
index b3f790d..65e2b20 100644
--- a/docs/html/distribute/monetize/ecommerce.jd
+++ b/docs/html/distribute/monetize/ecommerce.jd
@@ -44,12 +44,14 @@
   Account</a>.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="related-resources">
+<p style="clear:both">
+</p>
+<div class="headerLine">
+  <h2 id="related-resources">
     Related Resources
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/monetize/ecommerce"
diff --git a/docs/html/distribute/monetize/freemium.jd b/docs/html/distribute/monetize/freemium.jd
index ec86d19..0d33054 100644
--- a/docs/html/distribute/monetize/freemium.jd
+++ b/docs/html/distribute/monetize/freemium.jd
@@ -66,11 +66,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="related-resources">
+  <h2 id="related-resources">
   Related Resources
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="resource-widget resource-flow-layout col-13"
diff --git a/docs/html/distribute/monetize/payments.jd b/docs/html/distribute/monetize/payments.jd
index 37b4d44..55c289f 100644
--- a/docs/html/distribute/monetize/payments.jd
+++ b/docs/html/distribute/monetize/payments.jd
@@ -17,11 +17,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="dcb">
+  <h2 id="dcb">
   Direct Carrier Billing
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -33,10 +33,10 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="credit">
+  <h2 id="credit">
   Credit Cards
-  </h1>
-  <hr>
+  </h2>
+
 </div>
 
 <p>
@@ -47,12 +47,12 @@
   setup process.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="gift-cards">
+<div class="headerLine">
+  <h2 id="gift-cards">
   Google Play Gift Cards
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure">
@@ -67,12 +67,14 @@
   "_android">here</a>.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="balance">
+<p style="clear:both">
+</p>
+<div class="headerLine">
+  <h2 id="balance">
   Google Play Balance
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure">
@@ -94,7 +96,9 @@
   network, and other factors.
 </p>
  
-<div class="headerLine clearfloat"><h1 id="related-resources">Related Resources</h1><hr></div>
+<p style="clear:both">
+</p>
+<div class="headerLine"><h2 id="related-resources">Related Resources</h2></div>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/monetize/paymentmethods"
diff --git a/docs/html/distribute/monetize/premium.jd b/docs/html/distribute/monetize/premium.jd
index b66cd03..c8823d1 100644
--- a/docs/html/distribute/monetize/premium.jd
+++ b/docs/html/distribute/monetize/premium.jd
@@ -35,11 +35,13 @@
   <a href="{@docRoot}distribute/monetize/ads.html">advertising</a> models.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1>
+<p style="clear:both">
+</p>
+<div class="headerLine">
+  <h2>
   Related Resources
-  </h1>
-  <hr>
+  </h2>
+
 </div>
 
 <div class="resource-widget resource-flow-layout col-13"
diff --git a/docs/html/distribute/monetize/subscriptions.jd b/docs/html/distribute/monetize/subscriptions.jd
index a838e30..6a4d9b1 100644
--- a/docs/html/distribute/monetize/subscriptions.jd
+++ b/docs/html/distribute/monetize/subscriptions.jd
@@ -61,7 +61,9 @@
   </p>
 </div>
 
-<div class="headerLine clearfloat"><h1 id="related-resources">Related Resources</h1><hr></div>
+<p style="clear:both">
+</p>
+<div class="headerLine"><h2 id="related-resources">Related Resources</h2></div>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/monetize/subscriptions"
diff --git a/docs/html/distribute/tools/launch-checklist.jd b/docs/html/distribute/tools/launch-checklist.jd
index 3b0dd55..f310800 100644
--- a/docs/html/distribute/tools/launch-checklist.jd
+++ b/docs/html/distribute/tools/launch-checklist.jd
@@ -62,11 +62,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="understand-publishing">
+  <h2 id="understand-publishing">
     1. Understand the Publishing Process
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -85,9 +85,7 @@
   Play.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/launchchecklist/understanding"
@@ -95,12 +93,12 @@
 data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="understand-policies">
+<div class="headerLine">
+  <h2 id="understand-policies">
     2. Understand Google Play Policies and Agreements
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -110,21 +108,19 @@
   repeated violations, termination of your developer account.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/launchchecklist/policies" data-sortorder=
 "-timestamp" data-cardsizes="6x3" data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="test-quality">
+<div class="headerLine">
+  <h2 id="test-quality">
     3. Test for Quality
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -154,21 +150,19 @@
   should exhibit.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/launchchecklist/quality" data-sortorder=
 "-timestamp" data-cardsizes="6x3,6x3,6x3,9x3,9x3,9x3" data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="determine-rating">
+<div class="headerLine">
+  <h2 id="determine-rating">
     4. Determine your App’s Content Rating
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -212,21 +206,19 @@
   no changes are required in your app binary.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/launchchecklist/rating" data-sortorder=
 "-timestamp" data-cardsizes="9x3,6x3,6x3,9x3,9x3,9x3" data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="determine-country">
+<div class="headerLine">
+  <h2 id="determine-country">
     5. Determine Country Distribution
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -280,21 +272,19 @@
   Checklist</a> for key steps and considerations in the localization process.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/launchchecklist/country" data-sortorder=
 "-timestamp" data-cardsizes="9x3,9x3,6x3,9x3,9x3,9x3" data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="confirm-size">
+<div class="headerLine">
+  <h2 id="confirm-size">
     6. Confirm the App's Overall Size
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -335,21 +325,19 @@
   on your code when building your release-ready APK.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/launchchecklist/size" data-sortorder=
 "-timestamp" data-cardsizes="9x3,9x3,6x3,9x3,9x3,9x3" data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="confirm-platform">
+<div class="headerLine">
+  <h2 id="confirm-platform">
     7. Confirm the App's Platform and Screen Compatibility Ranges
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -387,21 +375,19 @@
   <a href="{@docRoot}about/dashboards/index.html">Device Dashboard</a> charts.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/launchchecklist/platform" data-sortorder=
 "-timestamp" data-cardsizes="6x3,6x3,6x3" data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="decide-price">
+<div class="headerLine">
+  <h2 id="decide-price">
     8. Decide Whether your App will be Free or Priced
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure">
@@ -450,21 +436,19 @@
   set up a Google Wallet Merchant Account</a> before you can publish.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/launchchecklist/price" data-sortorder=
 "-timestamp" data-cardsizes="9x3,9x3,6x3,9x3,9x3,9x3" data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="consider-billing">
+<div class="headerLine">
+  <h2 id="consider-billing">
     9. Consider using In-app Billing
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -485,9 +469,7 @@
   complete and test your implementation before creating your release-ready APK.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/launchchecklist/purchasemethod"
@@ -495,12 +477,12 @@
 data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="set-prices">
+<div class="headerLine">
+  <h2 id="set-prices">
     10. Set Prices for your Products
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -517,21 +499,19 @@
   available currencies through the Developer Console.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/launchchecklist/setprice" data-sortorder=
 "-timestamp" data-cardsizes="9x3,9x3,9x3,9x3,9x3,9x3" data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="start-localization">
+<div class="headerLine">
+  <h2 id="start-localization">
     11. Start Localization
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -599,9 +579,7 @@
   listing.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/launchchecklist/localization"
@@ -609,12 +587,12 @@
 data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="prepare-graphics">
+<div class="headerLine">
+  <h2 id="prepare-graphics">
     12. Prepare Promotional Graphics, Screenshots, and Videos
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -659,21 +637,19 @@
   publishing date.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/launchchecklist/graphics" data-sortorder=
 "-timestamp" data-cardsizes="9x3,9x3,6x3,9x3,9x3,9x3" data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="build-upload">
+<div class="headerLine">
+  <h2 id="build-upload">
     13. Build and Upload the Release-ready APK
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -701,7 +677,7 @@
   Developer Console. If necessary, you can replace an APK with a more recent
   version before publishing.
 </p>
-<!--<h3>Related resources</h3>
+<!--<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/toolsreference/launchchecklist/build"
@@ -709,12 +685,12 @@
   data-cardSizes="9x3,9x3,6x3,9x3,9x3,9x3"
   data-maxResults="6"></div>-->
 
-<div class="headerLine clearfloat">
-  <h1 id="plan-beta">
+<div class="headerLine">
+  <h2 id="plan-beta">
     14. Plan a Beta Release
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="sidebox-wrapper" style="float:right;">
@@ -765,11 +741,11 @@
 </table> -->
 
 <div class="headerLine">
-  <h1 id="complete-details">
+  <h2 id="complete-details">
     15. Complete the Apps’ Store Listing
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -815,9 +791,7 @@
   elsewhere.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/launchchecklist/productdetails"
@@ -825,12 +799,12 @@
 data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="use-badges">
+<div class="headerLine">
+  <h2 id="use-badges">
     16. Use Google Play Badges and Links in your Promotional Campaigns
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -853,21 +827,19 @@
   available.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/launchchecklist/badges" data-sortorder=
 "-timestamp" data-cardsizes="9x3,9x3,6x3,9x3,9x3,9x3" data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="final-checks">
+<div class="headerLine">
+  <h2 id="final-checks">
     17. Final Checks and Publishing
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -959,9 +931,7 @@
   linking from your promotional campaigns.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/launchchecklist/finalchecks"
@@ -969,12 +939,12 @@
 data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="support-users">
+<div class="headerLine">
+  <h2 id="support-users">
     18. Support Users after Launch
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -1063,7 +1033,7 @@
 </ul>
 </ul>
 
-<h3>Related resources</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/toolsreference/launchchecklist/afterlaunch"
diff --git a/docs/html/distribute/tools/localization-checklist.jd b/docs/html/distribute/tools/localization-checklist.jd
index 7a638ed..08a8143 100644
--- a/docs/html/distribute/tools/localization-checklist.jd
+++ b/docs/html/distribute/tools/localization-checklist.jd
@@ -41,11 +41,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="identify-languages">
+  <h2 id="identify-languages">
     1. Identify target languages and locales
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -84,21 +84,19 @@
   development, translation, testing, and marketing efforts to these markets.
 </p>
 
-<h3 id="related-resources">
-  Related Resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/localizationchecklist/identifylocales"
 data-sortorder="-timestamp" data-cardsizes="9x3," data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="design">
+<div class="headerLine">
+  <h2 id="design">
     2. Design for localization
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -224,21 +222,19 @@
   directories, without language or locale qualifiers.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/tools/loc/designforloc" data-sortorder="-timestamp"
 data-cardsizes="9x3" data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="manage-strings">
+<div class="headerLine">
+  <h2 id="manage-strings">
     3. Manage strings for localization
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -399,21 +395,19 @@
 
 &lt;/resources&gt;
 </pre>
-<h3 class="clearfloat">
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/localizationchecklist/managestrings"
 data-sortorder="-timestamp" data-cardsizes="9x3" data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="translate-strings">
+<div class="headerLine">
+  <h2 id="translate-strings">
     4. Translate UI strings and other resources
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -570,21 +564,19 @@
   <img src="{@docRoot}images/gp-localization-trans-0.png" class="border-img">
 </div>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/localizationchecklist/translatestrings"
 data-sortorder="-timestamp" data-cardsizes="9x3" data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="test">
+<div class="headerLine">
+  <h2 id="test">
     5. Test your localized app
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -690,7 +682,7 @@
   your localized apps. One way to do that is through beta testing with regional
   users &mdash; Google Play can help you do this. <!-- </p>
 
-<h3 class="clearfloat">Related resources</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/toolsreference/localizationchecklist/test"
@@ -699,12 +691,12 @@
   data-maxResults="6"></div> -->
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="prepare-launch">
+<div class="headerLine">
+  <h2 id="prepare-launch">
     6. Prepare for international launch
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -887,9 +879,7 @@
   helpful reminders for a successful localized launch.
 </p>
 
-<h3>
-  Related resources
-</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13" data-query=
 "collection:distribute/toolsreference/localizationchecklist/preplaunch"
@@ -897,12 +887,12 @@
 data-maxresults="6">
 </div>
 
-<div class="headerLine clearfloat">
-  <h1 id="support-users">
+<div class="headerLine">
+  <h2 id="support-users">
     7. Support international users after launch
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -968,7 +958,7 @@
   "{@docRoot}distribute/tools/launch-checklist.html">Launch Checklist</a> to
   learn more about how to plan, build, and launch your app on Google Play.
 </p>
-<h3 class="clearfloat">Related resources</h3>
+<h3 class="rel-resources clearfloat">Related resources</h3>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/toolsreference/localizationchecklist/supportlaunch"
diff --git a/docs/html/distribute/tools/open-distribution.jd b/docs/html/distribute/tools/open-distribution.jd
index f804af2..e28102d 100644
--- a/docs/html/distribute/tools/open-distribution.jd
+++ b/docs/html/distribute/tools/open-distribution.jd
@@ -26,11 +26,11 @@
 </p>
 
 <div class="headerLine">
-  <h1>
+  <h2>
   Distributing Through an App Marketplace
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -55,11 +55,11 @@
 </p>
 
 <div class="headerLine">
-  <h1>
+  <h2>
   Distributing Your Apps by Email
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure" style="width:300px;">
@@ -95,11 +95,11 @@
 </p>
 
 <div class="headerLine">
-  <h1>
+  <h2>
   Distributing Through a Website
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -119,12 +119,12 @@
   sources</a>.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1>
+<div class="headerLine">
+  <h2>
   User Opt-In for Apps from Unknown Sources
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure" style="width:325px;">
diff --git a/docs/html/distribute/tools/promote/badge-files.jd b/docs/html/distribute/tools/promote/badge-files.jd
index cce3632..e65e698 100644
--- a/docs/html/distribute/tools/promote/badge-files.jd
+++ b/docs/html/distribute/tools/promote/badge-files.jd
@@ -12,7 +12,7 @@
 <p>The following links provide the Adobe&reg; Illustrator&reg; (.ai) file for the
 two Google Play badges.</p>
 
-<hr>
+
 <img src="{@docRoot}images/brand/en_generic_rgb_wo_60.png" alt="Get It On Google Play">
 
 <div style="clear:left">&nbsp;</div>
@@ -21,9 +21,10 @@
 
        <a href="{@docRoot}downloads/brand/v2/english_get.ai">English (English)</a><br/>
 
+       <a href="{@docRoot}downloads/brand/af_generic_rgb_wo.ai">Afrikaans (Afrikaans)</a><br/>
+
        <a href="{@docRoot}downloads/brand/v2/amharic_get.ai">ኣማርኛ (Amharic)</a><br/>
 
-       <a href="{@docRoot}downloads/brand/af_generic_rgb_wo.ai">Afrikaans (Afrikaans)</a><br/>
 <!--
        <a href="{@docRoot}downloads/brand/ar_generic_rgb_wo.ai">العربية (Arabic)</a><br/>
 -->
@@ -137,7 +138,7 @@
 
 
 
-<hr>
+
 <img src="{@docRoot}images/brand/en_app_rgb_wo_60.png" alt="Android App On Google Play">
 
 <div style="clear:left">&nbsp;</div>
@@ -286,6 +287,3 @@
 <p>To quickly create a badge that links to your apps on Google Play,
 use the <a
 href="{@docRoot}distribute/tools/promote/badges.html">Googe Play badge generator</a>.</p>
-    
-    
-    
\ No newline at end of file
diff --git a/docs/html/distribute/tools/promote/brand.jd b/docs/html/distribute/tools/promote/brand.jd
index 2116c0f..9b9f9a3 100644
--- a/docs/html/distribute/tools/promote/brand.jd
+++ b/docs/html/distribute/tools/promote/brand.jd
@@ -9,6 +9,11 @@
 promotional materials. You can use the icons and other assets on this page
 provided that you follow the guidelines.</p>
 
+<p>Use of the Android or Google Play brands must be reviewed by the Android
+Partner Marketing team.  Use the <a
+href="https://docs.google.com/forms/d/1YE5gZpAAcFKjYcUddCsK1Bv9a9Y-luaLVnkazVlaJ2w/viewform">Android and Google Play Brand Permissions Inquiry form</a> to submit your
+marketing for review.</p>
+
 <h2 id="brand-android">Android</h2>
 
  <p>The following are guidelines for the Android brand
@@ -29,10 +34,12 @@
         <li><span style="color:red">Incorrect</span>: "Android MediaPlayer"</li>
         <li><span style="color:green">Correct</span>: "MediaPlayer for Android"</li>
       </ul>
-      <p>If used with your logo, "for Android" needs to be smaller in size than your logo.
+      <p>If used with your logo, "for Android" should be no larger than 90% of your logo’s size.
       First instance of this use should be followed by a TM symbol, "for Android&trade;".</p>
     </li>
-    <li>Android may be used as a descriptor, as long as it is followed by a proper generic term.
+    <li>Android may be used as a descriptor, as long as it is followed by a
+        proper generic term. (Think of "Android" as a term used in place of
+        "the Android platform.")
       <ul>
         <li><span style="color:red">Incorrect</span>: "Android MediaPlayer" or "Android XYZ app"</li>
         <li><span style="color:green">Correct</span>: "Android features" or "Android applications"</li>
@@ -62,14 +69,14 @@
 
     <p>When using the Android Robot or any modification of it, proper attribution is
     required under the terms of the <a href="http://creativecommons.org/licenses/by/3.0/">Creative
-Commons Attribution</a> license:</p>
+    Commons Attribution 3.0</a> license:</p>
    
     <blockquote><em>The Android robot is reproduced or modified from work created and shared by Google and
 used according to terms described in the Creative Commons 3.0 Attribution License.</em></blockquote>
     
-    <p>You may not file trademark applications incorporating the Android robot logo or
-derivatives thereof. We want to ensure that the Android robot remains available
-for all to use.</p>
+    <p>You may not file trademark applications incorporating the Android robot
+    logo or derivatives thereof within your company logo or business name. We
+    want to ensure that the Android robot remains available for all to use.</p>
 
 
 <h4 style="clear:right">Android logo</h4>
@@ -78,12 +85,10 @@
   <img alt="" src="{@docRoot}images/brand/android_logo_no.png">
 </div>
 
-<p>The Android logo may not be used. Nor can this be used with the Android robot.</p>
+<p>The Android logo may not be used.</p>
+
 <p>The custom typeface may not be used.</p>
 
-
-
-
 <h2 id="brand-google_play">Google Play</h2>
 
 
@@ -98,7 +103,7 @@
 <p>When referring to the mobile experience, use "Google Play" unless the text is clearly
 instructional for the user. For example, a marketing headline might read "Download our
 games on Google Play&trade;," but instructional text would read "Download our games using the Google
-Play&trade; Store app."
+Play&trade; store app."
 
  <p>Any use of the Google Play name or icon needs to include this
     attribution in your communication:</p>
@@ -111,16 +116,16 @@
     <p style="text-align:center">
        <a href="{@docRoot}images/brand/Google_Play_Store_48.png">48x48</a> |
        <a href="{@docRoot}images/brand/Google_Play_Store_96.png">96x96</a><br>
-       <a href="{@docRoot}downloads/brand/Google_Play_Store.ai">Illustrator (.ai)</a>
+       <a href="{@docRoot}images/brand/Google_Play_Store_600.png">600x576</a>
        </p>
   </div>
   
-<h4>Google Play Store icon</h4>
+<h4>Google Play store icon</h4>
 
-<p>You may use the Google Play Store icon, but you may not modify it.</p>
+<p>You may use the Google Play store icon, but you may not modify it.</p>
 
-<p>As mentioned above, when referring to the Google Play Store app in copy, use the full name:
-"Google Play Store." However, when labeling the Google Play Store icon directly, it's OK to use
+<p>As mentioned above, when referring to the Google Play store app in copy, use the full name:
+"Google Play store." However, when labeling the Google Play store icon directly, it's OK to use
 "Play Store" alone to accurately reflect the icon label as it appears on a device.</p>
 
         
@@ -140,9 +145,14 @@
        <a href="{@docRoot}images/brand/en_generic_rgb_wo_60.png">172x60</a></p>
   </div>
          
-  <p>The "Get it on Google Play" and "Android App on Google Play" logos are badges that you
-    can use on your website and promotional materials, to point to your products on Google
-    Play.</p>
+  <p>The "Get it on Google Play" and "Android App on Google Play" logos are
+    badges that you can use on your website and promotional materials, to point
+    to your products on Google Play. Additional Google Play badge formats and
+    badges for music, books, magazines, movies, and TV shows are also available.
+    Use the  <a
+    href="https://docs.google.com/forms/d/1YE5gZpAAcFKjYcUddCsK1Bv9a9Y-luaLVnkazVlaJ2w/viewform">Android
+    and Google Play Brand Permissions Inquiry form</a> to request
+    those badges.</p>
 
   <ul>
     <li>Don't modify the color, proportions, spacing, or any other aspect of the badge image.
@@ -163,7 +173,7 @@
   
   <p>To quickly create a badge that links to your apps on Google Play,
   use the <a
-  href="{@docRoot}distribute/tools/promote/badges.html">Googe Play badge generator</a>
+  href="{@docRoot}distribute/tools/promote/badges.html">Google Play badge generator</a>
   (provides the badge in over 40 languages).</p>
   
   <p>To create your own size, download an Adobe&reg; Illustrator&reg; (.ai) file for the
@@ -171,25 +181,11 @@
   badge in over 40 languages</a>.</p>
     
   <p>For details on all the ways that you can link to your product details page in Google Play, 
-    see <a href="{@docRoot}distribute/tools/promote/linking.html">Linking to your products</a></p>
+    see <a href="{@docRoot}distribute/tools/promote/linking.html">Linking to your products</a>.</p>
 
+<h2 id="Marketing_Review">Marketing Reviews and Brand Inquiries</h2>
 
-
-<h2 id="Questions">Questions</h2>
-
-<p>To view our full guidelines or for any further brand usage questions, please contact our
-Android Partner Marketing team:</p>
-<ul>
-  <li>For North and South America, please contact <a
-  href="mailto:android-brand-approvals@google.com?Subject=Brand%20Approval%20Questions"
-  >android-brand-approvals@google.com</a></li>
-
-  <li>For Europe and Emerging Markets, please contact <a
-  href="mailto:emea-android-brand@google.com?Subject=Brand%20Approval%20Questions"
-  >emea-android-brand@google.com</a></li>
-
-  <li>For Asia and Pacific-America, please contact <a
-  href="mailto:apac-android-brand-approvals@google.com?Subject=Brand%20Approval%20Questions"
-  >apac-android-brand-approvals@google.com</a></li>
-</ul>
-
+<p>Use the <a
+href="https://docs.google.com/forms/d/1YE5gZpAAcFKjYcUddCsK1Bv9a9Y-luaLVnkazVlaJ2w/viewform">Android
+and Google Play Brand Permissions Inquiry form</a> to submit any marketing
+reviews or brand inquires. Typical response time is at least one week.</p>
diff --git a/docs/html/distribute/tools/promote/device-art.jd b/docs/html/distribute/tools/promote/device-art.jd
index b0b5f84..a204ea1 100644
--- a/docs/html/distribute/tools/promote/device-art.jd
+++ b/docs/html/distribute/tools/promote/device-art.jd
@@ -12,7 +12,7 @@
 <p class="note"><strong>Note</strong>: Do <em>not</em> use graphics created here in your 1024x500
 feature image or screenshots for your Google Play app listing.</p>
 
-<hr>
+
 
 <div class="supported-browser">
 
@@ -28,7 +28,7 @@
   </div>
 </div>
 
-<hr>
+
 
 <div class="layout-content-row">
   <div class="layout-content-col span-3">
diff --git a/docs/html/distribute/users/build-buzz.jd b/docs/html/distribute/users/build-buzz.jd
index b76498e..412589f 100644
--- a/docs/html/distribute/users/build-buzz.jd
+++ b/docs/html/distribute/users/build-buzz.jd
@@ -65,11 +65,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="link-to-your-apps">
+  <h2 id="link-to-your-apps">
     Link to Your Apps in Google Play
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -101,12 +101,12 @@
   </li>
 </ul>
 
-<div class="headerLine clearfloat">
-  <h1 id="use-the-google-play-badge">
+<div class="headerLine">
+  <h2 id="use-the-google-play-badge">
     Use the Google Play Badge
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure" style="margin:0 3em;">
@@ -126,12 +126,12 @@
   also easy to make and available in multiple languages.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="cross-promote-from-your-other-apps">
+<div class="headerLine">
+  <h2 id="cross-promote-from-your-other-apps">
     Cross-Promote from Your Other Apps
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure-right">
@@ -158,11 +158,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="hold-a-contest">
+  <h2 id="hold-a-contest">
     Hold a Contest
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -180,11 +180,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="leverage-pr">
+  <h2 id="leverage-pr">
     Leverage PR
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -196,11 +196,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="use-social-media">
+  <h2 id="use-social-media">
     Use Social Media
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -220,12 +220,12 @@
   review your apps. A review on the right blog is a great promotion.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="publish-youtube-videos">
+<div class="headerLine">
+  <h2 id="publish-youtube-videos">
     Publish YouTube Videos
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="center-img" style="padding-top:1em;">
@@ -240,11 +240,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="advertise">
+  <h2 id="advertise">
     Advertise
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure">
@@ -262,12 +262,12 @@
   Sign up for an AdMob account</a> to get started.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="maximize-your-marketing-spend">
+<div class="headerLine">
+  <h2 id="maximize-your-marketing-spend">
     Maximize your Marketing Spend
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure" style="margin: 0 3em;">
@@ -285,12 +285,12 @@
   platforms simultaneously helps you maximize your return on investment.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="related-resources">
+<div class="headerLine">
+  <h2 id="related-resources">
     Related Resources
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="resource-widget resource-flow-layout col-13"
diff --git a/docs/html/distribute/users/build-community.jd b/docs/html/distribute/users/build-community.jd
index 1623939..5cdabea 100644
--- a/docs/html/distribute/users/build-community.jd
+++ b/docs/html/distribute/users/build-community.jd
@@ -43,11 +43,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="starting-your-community">
+  <h2 id="starting-your-community">
   Starting Your Community
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -116,11 +116,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="tools-to-build-your-community">
+  <h2 id="tools-to-build-your-community">
   Tools to Build Your Community
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -158,11 +158,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="managing-your-community">
+  <h2 id="managing-your-community">
   Managing Your Community
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure">
@@ -189,11 +189,11 @@
   versions or new apps to make them feel special.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="related-resources">
+<div class="headerLine">
+  <h2 id="related-resources">
   Related Resources
-  </h1>
-  <hr>
+  </h2>
+
 </div>
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/users/buildcommunity"
diff --git a/docs/html/distribute/users/expand-to-new-markets.jd b/docs/html/distribute/users/expand-to-new-markets.jd
index cc94a92..df1a0fb 100644
--- a/docs/html/distribute/users/expand-to-new-markets.jd
+++ b/docs/html/distribute/users/expand-to-new-markets.jd
@@ -77,11 +77,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="localize-your-product">
+  <h2 id="localize-your-product">
     Localize Your Product
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="sidebox-wrapper" style="float:right;">
@@ -124,20 +124,16 @@
   high-quality professional translations at competitive prices.
 </p>
 
-<div style="float:left; width:48%; padding:8px;">
-  <img src="{@docRoot}images/gp-listing-3.jpg">
-</div>
+<img src="{@docRoot}images/gp-listing-3.jpg" style="padding:8px 0">
 
-<div style="width:48%; padding:8px; float:left">
-  <img src="{@docRoot}images/gp-expand-2.jpg">
-</div>
+<img src="{@docRoot}images/gp-expand-2.jpg" style="padding:8px 0">
 
-<div class="headerLine clearfloat">
-  <h1 id="testing-and-support">
+<div class="headerLine">
+  <h2 id="testing-and-support">
     Testing and Support
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -162,11 +158,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="localize-your-google-play-listing">
+  <h2 id="localize-your-google-play-listing">
     Localize Your Google Play Store Listing
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="sidebox-wrapper" style="float:right;">
@@ -295,11 +291,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="marketing">
+  <h2 id="marketing">
     Marketing
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="figure">
@@ -316,7 +312,7 @@
   for each language you support.
 </p>
 
-<div class="headerLine clearfloat"><h1 id="related-resources">Related Resources</h1><hr></div>
+<div class="headerLine"><h2 id="related-resources">Related Resources</h2></div>
 
 <div class="resource-widget resource-flow-layout col-13"
   data-query="collection:distribute/getusers/expandnewmarkets"
diff --git a/docs/html/distribute/users/index.jd b/docs/html/distribute/users/index.jd
index 77ef609..a810f36 100644
--- a/docs/html/distribute/users/index.jd
+++ b/docs/html/distribute/users/index.jd
@@ -14,8 +14,8 @@
 
 <div class="resource-widget resource-flow-layout landing col-16"
   data-query="collection:distribute/users"
-  data-cardSizes="6x6, 6x6, 6x6, 9x6, 9x6, 6x6, 6x6, 6x6"
-  data-maxResults="8">
+  data-cardSizes="6x6"
+  data-maxResults="6">
 </div>
 
 <h3>Related resources</h3>
diff --git a/docs/html/distribute/users/know-your-user.jd b/docs/html/distribute/users/know-your-user.jd
index fb91a05..1fbcb9c 100644
--- a/docs/html/distribute/users/know-your-user.jd
+++ b/docs/html/distribute/users/know-your-user.jd
@@ -15,13 +15,9 @@
   when they use your app, and how often they return to it.
 </p>
 
-<div class="headerLine">
-  <h1 id="read-ratings-comments">
+  <h2 id="read-ratings-comments">
   Read Ratings Comments
-  </h1>
-
-  <hr>
-</div>
+  </h2>
 
 <p>
   The most obvious way to get to know your users is by reading review comments
@@ -46,11 +42,11 @@
 </div>
 
 <div class="headerLine">
-  <h1 id="start-community">
+  <h2 id="start-community">
   Start a Community
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -68,11 +64,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="create-survey">
+  <h2 id="create-survey">
   Create a Survey
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -92,11 +88,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="add-analytics">
+  <h2 id="add-analytics">
   Add Analytics to your Apps
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -121,11 +117,11 @@
 </div>
 
 <div class="headerLine">
-  <h1 id="use-google">
+  <h2 id="use-google">
   Use Google+
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -142,8 +138,8 @@
   <img src="{@docRoot}images/gp-your-user-2.jpg">
 </div>
 
-<div class="headerLine clearfloat">
-<h1 id="related-resources">Related Resources</h1><hr>
+<div class="headerLine">
+<h2 id="related-resources">Related Resources</h2>
 </div>
 
 <div class="resource-widget resource-flow-layout col-13"
diff --git a/docs/html/distribute/users/promote-with-ads.jd b/docs/html/distribute/users/promote-with-ads.jd
new file mode 100644
index 0000000..1e28ae1
--- /dev/null
+++ b/docs/html/distribute/users/promote-with-ads.jd
@@ -0,0 +1,45 @@
+page.title=Promote Your App with Ads
+page.metaDescription=Promote your app through AdMob, AdWords, and YouTube to find new users at the right moment.
+page.image=/images/gp-ads-console.jpg
+page.tags="users, ads, analytics"
+
+@jd:body
+
+<p>
+  AdMob is Google's advertising platform for mobile apps. You can use it to
+  monetize your app and promote your apps, and you can link your Google
+  Analytics account to AdMob so you can analyze your apps &mdash; all in one
+  place.
+</p>
+
+<p>
+  <a href="http://www.google.com/ads/admob/">AdMob</a> is the largest mobile ad
+  app network. But you get more than just massive scale: AdMob will soon help
+  you find the right users in related apps. If your app is for bicycling, AdMob
+  can promote your app on other fitness and cycling-related apps worldwide.
+  <a href=
+  "https://apps.admob.com/admob/signup?subid=us-en-et-dac&_adc=ww-ww-et-admob2&hl=en">
+  Sign up for AdMob</a>.
+</p>
+
+<p>
+  AdMob also offers new solutions to help you achieve app-related goals such as
+  downloads, re-engagement and in-app purchases using Google search and the
+  Google Display Network. These solutions include streamlined campaign creation
+  flows and tools to track performance across the entire app lifecycle.
+  <a href="https://support.google.com/adwords/answer/2549053?hl=en">Learn
+  More</a>.
+</p>
+<div style="margin-top:2em;">
+  <img src="{@docRoot}images/gp-ads-console.jpg">
+</div>
+
+<div class="headerLine">
+<h2 id="related-resources">Related Resources</h2>
+</div>
+
+<div class="resource-widget resource-flow-layout col-13"
+  data-query="collection:distribute/users/promotewithads"
+  data-sortOrder="-timestamp"
+  data-cardSizes="9x3"
+  data-maxResults="6"></div>
\ No newline at end of file
diff --git a/docs/html/distribute/users/users_toc.cs b/docs/html/distribute/users/users_toc.cs
index a2437a6..1f173cb 100644
--- a/docs/html/distribute/users/users_toc.cs
+++ b/docs/html/distribute/users/users_toc.cs
@@ -28,7 +28,12 @@
         </a>
     </div>
   </li>
-
+  <li class="nav-section">
+    <div class="nav-section empty" style="font-weight:normal"><a href="<?cs var:toroot?>distribute/users/promote-with-ads.html">
+          <span class="en">Promote with Ads</span>
+        </a>
+    </div>
+  </li>
 </ul>
 
 
diff --git a/docs/html/distribute/users/your-listing.jd b/docs/html/distribute/users/your-listing.jd
index cc72fff..f869950 100644
--- a/docs/html/distribute/users/your-listing.jd
+++ b/docs/html/distribute/users/your-listing.jd
@@ -13,11 +13,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="graphics-imagery">
+  <h2 id="graphics-imagery">
     Graphics &amp; Imagery Tips
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -94,12 +94,12 @@
   Featured-Image Guidelines</a>.
 </p>
 
-<div class="headerLine clearfloat">
-  <h1 id="localization-tips">
+<div class="headerLine">
+  <h2 id="localization-tips">
     Localization Tips
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <div class="sidebox-wrapper" style="float:right;">
@@ -125,16 +125,14 @@
   the growing international audience</a>.
 </p>
 
-<div style="float:left;">
   <img src="{@docRoot}images/gp-listing-3.jpg">
-</div>
 
-<div class="headerLine clearfloat">
-  <h1 id="keyword-tips">
+<div class="headerLine">
+  <h2 id="keyword-tips">
     Keyword Tips
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -146,11 +144,11 @@
 </p>
 
 <div class="headerLine">
-  <h1 id="app-indexing">
+  <h2 id="app-indexing">
     Sign Up for App Indexing
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -164,11 +162,11 @@
 </div>
 
 <div class="headerLine">
-  <h1 id="education-app">
+  <h2 id="education-app">
     Education App Tips
-  </h1>
+  </h2>
 
-  <hr>
+
 </div>
 
 <p>
@@ -181,7 +179,7 @@
 </p>
 
 <div class="headerLine">
-<h1 id="related-resources">Related Resources</h1><hr>
+<h2 id="related-resources">Related Resources</h2>
 </div>
 
 <div class="resource-widget resource-flow-layout col-13"
diff --git a/docs/html/google/play-services/setup.jd b/docs/html/google/play-services/setup.jd
index d502e8d..744e191 100644
--- a/docs/html/google/play-services/setup.jd
+++ b/docs/html/google/play-services/setup.jd
@@ -95,7 +95,9 @@
   <li>Open the <code>build.gradle</code> file inside your application module directory. 
       <p class="note"><strong>Note:</strong> Android Studio projects contain a top-level 
       <code>build.gradle</code> file and a <code>build.gradle</code> file for each module. 
-      Be sure to edit the file for your application module.</p></li>
+      Be sure to edit the file for your application module. See
+      <a href="{@docRoot}sdk/installing/studio-build.html">Building Your Project with
+      Gradle</a> for more information about Gradle.</p></li>
   <li>Add a new build rule under <code>dependencies</code> for the latest version of
 <code>play-services</code>. For example:
 <pre class="no-pretty-print">
diff --git a/docs/html/google/play/billing/billing_admin.jd b/docs/html/google/play/billing/billing_admin.jd
index 46ad905..5904b03 100644
--- a/docs/html/google/play/billing/billing_admin.jd
+++ b/docs/html/google/play/billing/billing_admin.jd
@@ -66,7 +66,8 @@
 </p>
 </div>
 
-<p>You can create a product list for any published application or any draft application that's been
+<p>You can create a product list for any published application, or any
+application in the alpha or beta channels, that's been
 uploaded and saved to the Developer Console. However, you must have a Google Wallet merchant
 account and the application's manifest must include the <code>com.android.vending.BILLING</code>
 permission. If an application's manifest does not include this permission, you will be able to edit
@@ -75,6 +76,13 @@
 <a href="{@docRoot}google/play/billing/billing_integrate.html#billing-permission">Updating Your
 Application's Manifest</a>.</p>
 
+<p class="note"><strong>Note:</strong> Previously you could test an app by
+uploading an unpublished "draft" version. This functionality is no longer
+supported; instead, you must publish it to the alpha or beta distribution
+channel. For more information, see <a
+href="{@docRoot}google/play/billing/billing_testing.html#draft_apps">Draft Apps
+are No Longer Supported</a>.
+
 <p>In addition, an application package can have only one product list. If you create a product
 list for an application, and you use the <a
 href="{@docRoot}google/play/publishing/multiple-apks.html">multiple APK feature</a> to distribute
diff --git a/docs/html/google/play/billing/billing_testing.jd b/docs/html/google/play/billing/billing_testing.jd
index df6c657..8a49433 100644
--- a/docs/html/google/play/billing/billing_testing.jd
+++ b/docs/html/google/play/billing/billing_testing.jd
@@ -8,8 +8,9 @@
   <h2>In this document</h2>
   <ol>
     <li><a href="#testing-purchases">Testing In-app Purchases</a></li>
-        <li><a href="#billing-testing-static">Testing with static responses</a></li>
+    <li><a href="#billing-testing-static">Testing with Static Responses</a></li>
     <li><a href="#billing-testing-real">Setting Up for Test Purchases</a></li>
+    <li><a href="#draft_apps">Draft Apps are No Longer Supported</a></li>
   </ol>
   <h2>See also</h2>
   <ol>
@@ -79,8 +80,7 @@
 <p>First, upload and publish in-app products that you want testers to be able to
 purchase. You can upload and publish in-app products in the Developer Console. 
 Note that you can upload and publish your in-app items before you publish the
-APK itself. For example, you can publish your in-app items while your APK is
-still a draft. </p>
+APK itself.</p>
 
 <p>Next, create license test accounts for authorized users. In the Developer
 Console, go to <strong>Settings</strong> &gt; <strong>Account details</strong>,
@@ -149,11 +149,12 @@
 only be able to make test purchases. </p>
 
 
-<h2 id="billing-testing-static">Testing with static responses</h2>
+<h2 id="billing-testing-static">Testing with Static Responses</h2>
 
 <p>We recommend that you first test your In-app Billing implementation using static responses from
 Google Play. This enables you to verify that your application is handling the primary Google
-Play responses correctly and that your application is able to verify signatures correctly.</p>
+Play responses correctly and that your application is able to verify signatures correctly. You can do this
+even if the app hasn't been published yet.</p>
 
 <p>To test your implementation with static responses, you make an In-app Billing request using a
 special item that has a reserved product ID. Each reserved product ID returns a specific static
@@ -173,6 +174,12 @@
 install your application on a device, log into the device, and make billing requests using the
 reserved product IDs.</p>
 
+<p class="note"><strong>Note:</strong> Previously you could test an app by
+uploading an unpublished "draft" version. This functionality is no longer
+supported. However, you can test your app with static responses even before you
+upload it to the Google Play store. For more information, see <a
+href="#draft_apps">Draft Apps are No Longer Supported</a>.
+
 <p>There are four reserved product IDs for testing static In-app Billing responses:</p>
 
 <ul>
@@ -205,67 +212,12 @@
   </li>
 </ul>
 
-<p>In some cases, the reserved items may return signed static responses, which lets you test
-signature verification in your application. To test signature verification with the special reserved
-product IDs, you may need to set up <a
-href="{@docRoot}google/play/billing/billing_admin.html#billing-testing-setup">test accounts</a> or
-upload your application as a unpublished draft application. Table 1 shows you the conditions under
-which static responses are signed.</p>
-
-<p class="table-caption" id="static-responses-table"><strong>Table 1.</strong>
-Conditions under which static responses are signed.</p>
-
-<table>
-<tr>
-<th>Application ever been published?</th>
-<th>Draft application uploaded and unpublished?</th>
-<th>User who is running the application</th>
-<th>Static response signature</th>
-</tr>
-
-<tr>
-<td>No</td>
-<td>No</td>
-<td>Any</td>
-<td>Unsigned</td>
-</tr>
-
-<tr>
-<td>No</td>
-<td>No</td>
-<td>Developer</td>
-<td>Signed</td>
-</tr>
-
-<tr>
-<td>Yes</td>
-<td>No</td>
-<td>Any</td>
-<td>Unsigned</td>
-</tr>
-
-<tr>
-<td>Yes</td>
-<td>No</td>
-<td>Developer</td>
-<td>Signed</td>
-</tr>
-
-<tr>
-<td>Yes</td>
-<td>No</td>
-<td>Test account</td>
-<td>Signed</td>
-</tr>
-
-<tr>
-<td>Yes</td>
-<td>Yes</td>
-<td>Any</td>
-<td>Signed</td>
-</tr>
-
-</table>
+<p>In some cases, the reserved items may return signed static responses, which
+lets you test signature verification in your application. The reserved items
+only return signed responses if the user running the application has a <a
+href="{@docRoot}distribute/googleplay/start.html">developer</a> or <a
+href="{@docRoot}google/play/billing/billing_admin.html#billing-testing-setup">test
+account.</a>
 
 <p>To make an In-app Billing request with a reserved product ID, you simply construct a normal
 <code>REQUEST_PURCHASE</code> request, but instead of using a real product ID from your
@@ -310,9 +262,11 @@
 experience, including the actual purchases from Google Play and the actual checkout flow that
 users will experience in your application.</p>
 
-<p class="note"><strong>Note</strong>: You do not need to publish your application to do end-to-end
-testing. You only need to upload your application as a draft application to perform end-to-end
-testing.</p>
+<p class="note"><strong>Note:</strong> You can do end-to-end testing of your app
+  by publishing it to an <a
+  href="{@docRoot}distribute/googleplay/developer-console.html#alpha-beta">alpha
+  distribution channel</a>. This allows you to publish the app to the Google
+  Play store, but limit its availability to just the testers you designate. </p>
 
 <p>To test your In-app Billing implementation with actual in-app purchases, you will need to
 register at least one test account on the Google Play Developer Console. You cannot use your
@@ -327,14 +281,16 @@
 <p>To test your In-app Billing implementation with actual purchases, follow these steps:</p>
 
 <ol>
-  <li><strong>Upload your application as a draft application to the Developer Console.</strong>
-    <p>You do not need to publish your application to perform end-to-end testing with real product
-    IDs; you only need to upload your application as a draft application. However, you must sign
-    your application with your release key before you upload it as a draft application. Also, the
-    version number of the uploaded application must match the version number of the application you
-    load to your device for testing. To learn how to upload an application to Google Play, see
-    <a href="http://support.google.com/googleplay/android-developer/bin/answer.py?hl=en&answer=113469">Uploading
-    applications</a>.</p>
+  <li><strong>Upload your application to the <a
+  href="{@docRoot}distribute/googleplay/developer-console.html#alpha-beta">alpha
+  distribution channel</a> with the Developer Console.</strong>
+
+   <p class="note"><strong>Note:</strong> Previously you could test an app by
+    uploading an unpublished "draft" version. This functionality is no longer
+    supported; instead, you must publish it to the alpha or beta distribution
+    channel. For more information, see <a href="#draft_apps">Draft Apps are No
+    Longer Supported</a>.
+
   </li>
   <li><strong>Add items to the application's product list.</strong>
     <p>Make sure that you publish the items (the application can remain unpublished). See <a
@@ -370,3 +326,24 @@
 href="{@docRoot}distribute/tools/launch-checklist.html">publishing on Google Play</a>.
 </p>
 
+<h2 id="draft_apps">Draft Apps are No Longer Supported</h2>
+
+<p>Previously, you could publish a "draft" version of your app for testing. This
+functionality is no longer supported. Instead, there are two ways you can test
+how a pre-release app functions on the Google Play store:</p>
+
+<ul>
+
+  <li>You can publish an app to the <a
+  href="{@docRoot}distribute/googleplay/developer-console.html#alpha-beta">alpha
+  or beta distribution channels</a>. This makes the app available on the Google
+  Play store, but only to the testers you put on a "whitelist".</li>
+
+  <li>In a few cases, you can test Google Play functionality with an unpublished
+  app. For example, you can test an unpublished app's in-app billing support by
+  using <a
+  href="{@docRoot}google/play/billing/billing_testing.html#billing-testing-static">static
+  responses</a>, special reserved product IDs that always return a specific
+  result (like "purchased" or "refunded").</li>
+
+</ul>
diff --git a/docs/html/google/play/billing/v2/billing_integrate.jd b/docs/html/google/play/billing/v2/billing_integrate.jd
index ca41e0b..5eb17d5 100644
--- a/docs/html/google/play/billing/v2/billing_integrate.jd
+++ b/docs/html/google/play/billing/v2/billing_integrate.jd
@@ -208,6 +208,14 @@
 a draft to the Google Play Developer Console. You also need to create a product list for the in-app
 items that are available for purchase in the sample application. The following instructions show you
 how to do this.</p>
+
+<p class="caution"><strong>Caution:</strong> Draft applications are no longer
+supported. To test an application, publish it in the <a
+href="{@docRoot}distribute/googleplay/developer-console.html#alpha-beta">alpha
+or beta channels</a>. For more information, see <a
+href="{@docRoot}google/play/billing/billing_testing.html#draft_apps">Draft Apps
+are No Longer Supported</a>.</p>
+
 <ol>
   <li><strong>Upload the release version of the sample application to Google Play.</strong>
     <p>Do not publish the sample application; leave it as an unpublished draft application. The
@@ -928,10 +936,12 @@
   // Intent actions that we receive in the BillingReceiver from Google Play.
   // These are defined by Google Play and cannot be changed.
   // The sample application defines these in the Consts.java file.
-  public static final String ACTION_NOTIFY = "com.android.vending.billing.IN_APP_NOTIFY";
-  public static final String ACTION_RESPONSE_CODE = "com.android.vending.billing.RESPONSE_CODE";
+  public static final String ACTION_NOTIFY =
+      "com.android.vending.billing.IN_APP_NOTIFY";
+  public static final String ACTION_RESPONSE_CODE =
+      "com.android.vending.billing.RESPONSE_CODE";
   public static final String ACTION_PURCHASE_STATE_CHANGED =
-    "com.android.vending.billing.PURCHASE_STATE_CHANGED";
+      "com.android.vending.billing.PURCHASE_STATE_CHANGED";
 
   // The intent extras that are passed in an intent from Google Play.
   // These are defined by Google Play and cannot be changed.
@@ -962,7 +972,8 @@
       Log.w(TAG, "unexpected action: " + action);
     }
   }
-  // Perform other processing here, such as forwarding intent messages to your local service.
+  // Perform other processing here, such as forwarding intent messages
+  // to your local service.
 }
 </pre>
 
diff --git a/docs/html/google/play/expansion-files.jd b/docs/html/google/play/expansion-files.jd
index e90f8fa..601ea48 100644
--- a/docs/html/google/play/expansion-files.jd
+++ b/docs/html/google/play/expansion-files.jd
@@ -527,17 +527,21 @@
     &lt;!-- Required to download files from Google Play -->
     &lt;uses-permission android:name="android.permission.INTERNET" />
 
-    &lt;!-- Required to keep CPU alive while downloading files (NOT to keep screen awake) -->
+    &lt;!-- Required to keep CPU alive while downloading files
+        (NOT to keep screen awake) -->
     &lt;uses-permission android:name="android.permission.WAKE_LOCK" />
 
-    &lt;!-- Required to poll the state of the network connection and respond to changes -->
-    &lt;uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    &lt;!-- Required to poll the state of the network connection
+        and respond to changes -->
+    &lt;uses-permission
+        android:name="android.permission.ACCESS_NETWORK_STATE" />
 
     &lt;!-- Required to check whether Wi-Fi is enabled -->
     &lt;uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
 
     &lt;!-- Required to read and write the expansion files on shared storage -->
-    &lt;uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    &lt;uses-permission
+        android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     ...
 &lt;/manifest>
 </pre>
@@ -650,8 +654,8 @@
     &#64;Override
     public void onReceive(Context context, Intent intent) {
         try {
-            DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent,
-                    SampleDownloaderService.class);
+            DownloaderClientMarshaller.startDownloadServiceIfRequired(context,
+                intent, SampleDownloaderService.class);
         } catch (NameNotFoundException e) {
             e.printStackTrace();
         }
@@ -693,16 +697,19 @@
     <p>For example, the sample app provided in the Apk Expansion package calls the
 following method in the activity's {@link android.app.Activity#onCreate onCreate()} method to check
 whether the expansion files already exist on the device:</p>
+
 <pre>
 boolean expansionFilesDelivered() {
     for (XAPKFile xf : xAPKS) {
-        String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsBase, xf.mFileVersion);
+        String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsBase,
+            xf.mFileVersion);
         if (!Helpers.doesFileExist(this, fileName, xf.mFileSize, false))
             return false;
     }
     return true;
 }
 </pre>
+
     <p>In this case, each {@code XAPKFile} object holds the version number and file size of a known
 expansion file and a boolean as to whether it's the main expansion file. (See the sample
 application's {@code SampleDownloaderActivity} class for details.)</p>
@@ -740,6 +747,7 @@
 display the download progress (see the next step). If the response <em>is</em> {@code
 NO_DOWNLOAD_REQUIRED}, then the files are available and your application can start.</p>
     <p>For example:</p>
+
 <pre>
 &#64;Override
 public void onCreate(Bundle savedInstanceState) {
@@ -754,11 +762,14 @@
                 notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
 
         // Start the download service (if required)
-        int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
+        int startResult =
+            DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
                         pendingIntent, SampleDownloaderService.class);
-        // If download has started, initialize this activity to show download progress
+        // If download has started, initialize this activity to show
+        // download progress
         if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
-            // This is where you do set up to display the download progress (next step)
+            // This is where you do set up to display the download
+            // progress (next step)
             ...
             return;
         } // If the download wasn't necessary, fall through to start the app
@@ -766,6 +777,7 @@
     startApp(); // Expansion files are available, start the app
 }
 </pre>
+
   </li>
   <li>When the {@code startDownloadServiceIfRequired()} method returns anything <em>other
 than</em> {@code NO_DOWNLOAD_REQUIRED}, create an instance of {@code IStub} by
@@ -783,9 +795,11 @@
 starts the download. </p>
     <p>For example, in the previous code sample for {@link android.app.Activity#onCreate
 onCreate()}, you can respond to the {@code startDownloadServiceIfRequired()} result like this:</p>
+
 <pre>
         // Start the download service (if required)
-        int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
+        int startResult =
+            DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
                         pendingIntent, SampleDownloaderService.class);
         // If download has started, initialize activity to show progress
         if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
@@ -892,7 +906,8 @@
 expansion files. You might want to provide a user preference to enable downloads over
 the cellular network. In which case, you can call:
 <pre>
-mRemoteService.setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);
+mRemoteService
+    .setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);
 </pre>
 </dd>
 </dl>
@@ -975,10 +990,12 @@
 // The shared path to all app expansion files
 private final static String EXP_PATH = "/Android/obb/";
 
-static String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) {
+static String[] getAPKExpansionFiles(Context ctx, int mainVersion,
+      int patchVersion) {
     String packageName = ctx.getPackageName();
     Vector&lt;String> ret = new Vector&lt;String>();
-    if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+    if (Environment.getExternalStorageState()
+          .equals(Environment.MEDIA_MOUNTED)) {
         // Build the full path to the app's expansion files
         File root = Environment.getExternalStorageDirectory();
         File expPath = new File(root.toString() + EXP_PATH + packageName);
@@ -1102,7 +1119,8 @@
 
 <pre>
 // Get a ZipResourceFile representing a merger of both the main and patch files
-ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(appContext,
+ZipResourceFile expansionFile =
+    APKExpansionSupport.getAPKExpansionZipFile(appContext,
         mainVersion, patchVersion);
 
 // Get an input stream for a known file inside the expansion file ZIPs
@@ -1190,28 +1208,18 @@
 opens, it's important that you test this process to be sure your application can successfully query
 for the URLs, download the files, and save them to the device.</p>
 
-<p>To test your application's implementation of the manual download procedure, you must upload
-your application to Google Play as a "draft" to make your expansion files available for
-download:</p>
-
-<ol>
-  <li>Upload your APK and corresponding expansion files using the Google Play Developer
-Console.</li>
-  <li>Fill in the necessary application details (title, screenshots, etc.). You can come back and
-finalize these details before publishing your application.
-  <p>Click the <strong>Save</strong> button. <em>Do not click Publish.</em> This saves
-the application as a draft, such that your application is not published for Google Play users,
-but the expansion files are available for you to test the download process.</p></li>
-  <li>Install the application on your test device using the Eclipse tools or <a
-href="{@docRoot}tools/help/adb.html">{@code adb}</a>.</li>
-  <li>Launch the app.</li>
-</ol>
-
-<p>If everything works as expected, your application should begin downloading the expansion
+<p>To test your application's implementation of the manual download procedure,
+you can publish it to the alpha or beta channel, so it will only be available to
+authorized testers.
+If everything works as expected, your application should begin downloading the expansion
 files as soon as the main activity starts.</p>
 
-
-
+<p class="note"><strong>Note:</strong> Previously you could test an app by
+uploading an unpublished "draft" version. This functionality is no longer
+supported; instead, you must publish it to the alpha or beta distribution
+channel. For more information, see <a
+href="{@docRoot}google/play/billing/billing_testing.html#draft_apps">Draft Apps
+are No Longer Supported</a>.
 
 <h2 id="Updating">Updating Your Application</h2>
 
diff --git a/docs/html/google/play/licensing/licensing-reference.jd b/docs/html/google/play/licensing/licensing-reference.jd
index 7bfa61a..d4ca79a 100644
--- a/docs/html/google/play/licensing/licensing-reference.jd
+++ b/docs/html/google/play/licensing/licensing-reference.jd
@@ -151,7 +151,8 @@
 <tr>
 <td>{@code LICENSED}</td>
 <td>The application is licensed to the user. The user has purchased the
-application or the application only exists as a draft.</td>
+application, or is authorized to download and install the alpha or beta version
+of the application.</td>
 <td>Yes</td>
 <td><code>VT</code>,&nbsp;<code>GT</code>, <code>GR</code></td>
 <td><em>Allow access according to {@code Policy} constraints.</em></td>
@@ -233,16 +234,14 @@
 href="{@docRoot}google/play/licensing/setting-up.html#test-env">
 Setting Up The Testing Environment</a>, the response code can be manually
 overridden for the application developer and any registered test users via the
-Google Play Developer Console.
-<br/><br/>
-Additionally, as noted above, applications that are in draft mode (in other
-words, applications that have been uploaded but have <em>never</em> been
-published) will return {@code LICENSED} for all users, even if not listed as a test
-user. Since the application has never been offered for download, it is assumed
-that any users running it must have obtained it from an authorized channel for
-testing purposes.</p>
+Google Play Developer Console.</p>
 
-
+<p class="note"><strong>Note:</strong> Previously you could test an app by
+uploading an unpublished "draft" version. This functionality is no longer
+supported; instead, you must publish it to the alpha or beta distribution
+channel. For more information, see <a
+href="{@docRoot}google/play/billing/billing_testing.html#draft_apps">Draft Apps
+are No Longer Supported</a>.
 
 
 <h2 id="extras">Server Response Extras</h2>
@@ -430,8 +429,8 @@
         }
     } else if (mLastResponse == LicenseResponse.RETRY &amp;&amp;
                 ts &lt; mLastResponseTime + MILLIS_PER_MINUTE) {
-        // Only allow access if we are within the retry period or we haven't used up our
-        // max retries.
+        // Only allow access if we are within the retry period
+        // or we haven't used up our max retries.
         return (ts &lt;= mRetryUntil || mRetryCount &lt;= mMaxRetries);
     }
     return false;
diff --git a/docs/html/google/play/licensing/overview.jd b/docs/html/google/play/licensing/overview.jd
index 4e1a9c9..a2d5379 100644
--- a/docs/html/google/play/licensing/overview.jd
+++ b/docs/html/google/play/licensing/overview.jd
@@ -38,12 +38,11 @@
 the result to your application, which can allow or disallow further use of the
 application as needed.</p>
 
-<p class="note"><strong>Note:</strong> If a paid application has been uploaded 
-to Google Play, but saved only as a draft application (the app is 
-unpublished), the licensing server considers all users to be licensed users of 
-the application (because it's not even possible to purchase the app). This 
-exception is necessary in order for you to perform testing of your licensing 
-implementation.</p>
+<p class="note"><strong>Note:</strong> If a version of an app is in the alpha or
+beta channel, all users who are authorized to download and install that app are
+considered to be licensed users of the app. For more information, see <a
+href="{@docRoot}distribute/googleplay/developer-console.html#alpha-beta">Alpha
+and Beta Testing</a>.</p>
 
 <div class="figure" style="width:469px">
 <img src="{@docRoot}images/licensing_arch.png" alt=""/>
@@ -52,6 +51,12 @@
 client, which handles communication with the Google Play server.</p>
 </div>
 
+<p class="note"><strong>Note:</strong> Previously you could test an app by
+uploading an unpublished "draft" version. This functionality is no longer
+supported; instead, you must publish it to the alpha or beta distribution
+channel. For more information, see <a
+href="{@docRoot}google/play/billing/billing_testing.html#draft_apps">Draft Apps
+are No Longer Supported</a>.
 
 <p>To properly identify the user and determine the license status, the licensing server requires
 information about the application and user&mdash;your application and the Google Play client work
diff --git a/docs/html/guide/components/fundamentals.jd b/docs/html/guide/components/fundamentals.jd
index 9ac063e..fd1a7a8 100644
--- a/docs/html/guide/components/fundamentals.jd
+++ b/docs/html/guide/components/fundamentals.jd
@@ -335,8 +335,8 @@
 {@link android.content.Intent} to start activities, services, and broadcast receivers. You can do so
 by explicitly naming the target component (using the component class name) in the intent. However,
 the real power of intents lies in the concept of <em>implicit intents</em>. An implicit intent
-simply describe the type of action to perform (and optionally, the data upon which you’d like to
-perform the action) and allow the system to find a component on the device that can perform the
+simply describes the type of action to perform (and, optionally, the data upon which you’d like to
+perform the action) and allows the system to find a component on the device that can perform the
 action and start it. If there are multiple components that can perform the action described by the
 intent, then the user selects which one to use.</p>
 
diff --git a/docs/html/guide/practices/verifying-apps-art.jd b/docs/html/guide/practices/verifying-apps-art.jd
new file mode 100644
index 0000000..0eedfaf
--- /dev/null
+++ b/docs/html/guide/practices/verifying-apps-art.jd
@@ -0,0 +1,296 @@
+page.title=Verifying App Behavior on the Android Runtime (ART)
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>Quickview</h2>
+  <ul>
+    <li>The new Android runtime (ART) is available on some of the newest Android
+      devices, though all of them currently have Dalvik as the default
+      runtime.</li>
+    <li>App developers should make sure their apps are compatible with ART,
+      especially if you use JNI to run native code or if you use certain tools
+      that produce non-standard code (such as some obfuscators).</li>
+  </ul>
+
+  <h2 id="Contents">In this document</h2>
+ <ol>
+    <li><a href="#GC_Migration">Addressing Garbage Collection (GC) Issues</a></li>
+    <li><a href="#JNI_Issues">Preventing JNI Issues</a>
+      <ol>
+        <li><a href="#JNI_and_GC">Checking JNI code for garbage-collection
+          issues</a></li>
+        <li><a href="#Error_Handling">Error handling</a></li>
+        <li><a href="#Object_Model_Changes">Object model changes</a></li>
+      </ol>
+    </li>
+    <li><a href="#Stack_Size">Preventing Stack Size Issues</a></li>
+    <li><a href="#AOT_Fails">Fixing AOT Compilation Issues</a></li>
+    <li><a href="#Reporting_Problems">Reporting Problems</a></li>
+  </ol>
+  <h2>See also</h2>
+  <ol>
+    <li><a href="http://source.android.com/devices/tech/dalvik/art.html">Introducing ART</a></li>
+    <li><a
+href="http://android-developers.blogspot.com/2011/07/debugging-android-jni-with-checkjni.html">Debugging
+Android JNI with CheckJNI</a></li>
+  </ol>
+</div>
+</div>
+
+<p>With Android 4.4, we are beginning to roll out a new Android runtime,
+<strong>ART</strong>. This runtime offers a number of new features that improve
+performance and smoothness of the Android platform and apps. (You can find more
+information about ART's new features in <a
+href="http://source.android.com/devices/tech/dalvik/art.html">Introducing
+ART</a>.)</p>
+
+<p>Currently, ART is available on a number of Android 4.4 devices, such as the
+Nexus 4, Nexus 5, Nexus 7, and Google Play edition devices.
+At this time, all devices still use Dalvik as the default runtime. We encourage
+you to test your apps for ART compatibility and to take advantage of ART's new
+features. However, for the time being, you should also take care to maintain
+compatibility with Dalvik.</p>
+
+<p>This document lets you know about things to watch for when migrating an
+existing app to be compatible with ART. Most apps should just work when
+running with ART. However, some techniques that work on Dalvik do not work on
+ART. This document discusses some of these issues.</p>
+
+<h2 id="GC_Migration">Addressing Garbage Collection (GC) Issues</h2>
+
+<p>Under Dalvik, apps frequently find it useful to explicitly call {@link
+java.lang.System#gc() System.gc()} to prompt garbage collection (GC). This should be
+far less necessary with ART, particularly if you're invoking garbage collection
+to prevent <a
+href="{@docRoot}/tools/debugging/debugging-memory.html#LogMessages"><code>GC_FOR_ALLOC</code></a>-type
+occurrences or to reduce fragmentation. You can verify which runtime is in use
+by calling {@link java.lang.System#getProperty(java.lang.String)
+System.getProperty("dalvik.vm.version")}. If ART is in use, the property's value
+is <code>"2.0.0"</code> or higher.</p>
+
+<p>Furthermore, a compacting garbage collector is under development in the <a
+href="https://source.android.com">Android Open-Source Project (AOSP)</a> to
+improve memory management. Because of this, you should avoid using techniques
+that are incompatible with compacting GC (such as saving pointers to object
+instance data). This is particularly important for apps that make use of the
+Java Native Interface (JNI). For more information, see <a
+href="#JNI_Issues">Preventing JNI Issues</a>.</p>
+
+<h2 id="JNI_Issues">Preventing JNI Issues</h2>
+
+<p>ART's JNI is somewhat stricter than Dalvik's. It is an especially good idea
+to use CheckJNI mode to catch common problems. If your app makes use of C/C++
+code, you should review the following article:</p>
+
+<p><a
+href="http://android-developers.blogspot.com/2011/07/debugging-android-jni-with-checkjni.html">Debugging
+Android JNI with CheckJNI</a></p>
+
+<h3 id="JNI_and_GC">Checking JNI code for garbage-collection issues</h3>
+
+<p>ART has a compacting garbage collector under development on the
+Android Open Source Project (AOSP). Once the compacting garbage collector is in
+use, objects may be moved in memory. If you use C/C++ code, do not
+perform operations that are incompatible with compacting GC. We have enhanced
+CheckJNI to identify some potential issues (as described in <a
+href="http://android-developers.blogspot.com/2011/11/jni-local-reference-changes-in-ics.html">JNI
+Local Reference Changes in ICS</a>).</p>
+
+<p>One area to watch for in particular is the use of
+<code>Get...ArrayElements()</code> and <code>Release...ArrayElements()</code>
+functions. In runtimes with non-compacting GC, the
+<code>Get...ArrayElements()</code> functions typically return a reference to the
+actual memory backing the array object. If you make a change to one of the
+returned array elements, the array object is itself changed (and the arguments
+to <code>Release...ArrayElements()</code> are usually ignored). However, if
+compacting GC is in use, the <code>Get...ArrayElements()</code> functions may
+return a copy of the memory. If you misuse the reference when compacting GC is
+in use, this can lead to memory corruption or other problems. For example:</p>
+
+<ul>
+
+  <li>If you make any changes to the returned array elements, you must call the
+  appropriate <code>Release...ArrayElements()</code> function when you are done,
+  to make sure the changes you made are correctly copied back to the underlying
+  array object.</li>
+
+  <li>When you release the memory array elements, you must use the appropriate
+  mode, depending on what changes you made:
+
+    <ul>
+
+      <li>If you did not make any changes to the array elements, use
+      <code>JNI_ABORT</code> mode, which releases the memory without copying
+      changes back to the underlying array object.</li>
+
+      <li>If you made changes to the array, and do not need the reference any
+      more, use code <code>0</code> (which updates the array object and frees
+      the copy of the memory).</li>
+
+      <li>If you made changes to the array that you want to commit, and you want
+      to keep the copy of the array, use <code>JNI_COMMIT</code> (which updates
+      the underlying array object and retains the copy).</li>
+
+    </ul>
+
+  </li>
+
+  <li>When you call <code>Release...ArrayElements()</code>, return the same
+  pointer that was originally returned by <code>Get...ArrayElements()</code>. For
+  example, it's not safe to increment the original pointer (to scan through the
+  returned array elements) then pass the incremented pointer to
+  <code>Release...ArrayElements()</code>. Passing this modified pointer can cause
+  the wrong memory to be freed, resulting in memory corruption.</li>
+
+</ul>
+
+<h3 id="Error_Handling">Error handling</h3>
+
+<p>ART's JNI throws errors in a number of cases where Dalvik didn’t. (Once
+again, you can catch many such cases by testing with CheckJNI.)</p>
+
+<p>For example, if <code>RegisterNatives</code> is called with a method that
+does not exist (perhaps because the method was removed by a tool such as
+<strong>ProGuard</strong>), ART now properly throws {@link
+java.lang.NoSuchMethodError}:</p>
+
+<pre class="no-pretty-print">
+08-12 17:09:41.082 13823 13823 E AndroidRuntime: FATAL EXCEPTION: main
+08-12 17:09:41.082 13823 13823 E AndroidRuntime: java.lang.NoSuchMethodError:
+    no static or non-static method
+    "Lcom/foo/Bar;.native_frob(Ljava/lang/String;)I"
+08-12 17:09:41.082 13823 13823 E AndroidRuntime:
+    at java.lang.Runtime.nativeLoad(Native Method)
+08-12 17:09:41.082 13823 13823 E AndroidRuntime:
+    at java.lang.Runtime.doLoad(Runtime.java:421)
+08-12 17:09:41.082 13823 13823 E AndroidRuntime:
+    at java.lang.Runtime.loadLibrary(Runtime.java:362)
+08-12 17:09:41.082 13823 13823 E AndroidRuntime:
+    at java.lang.System.loadLibrary(System.java:526)
+</pre>
+
+<p>ART also logs an error (visible in logcat) if <code>RegisterNatives</code> is
+called with no methods:</p>
+
+<pre class="no-pretty-print">
+W/art     ( 1234): JNI RegisterNativeMethods: attempt to register 0 native
+methods for &lt;classname>
+</pre>
+
+<p>In addition, the JNI functions <code>GetFieldID()</code> and
+<code>GetStaticFieldID()</code> now properly throw {@link java.lang.NoSuchFieldError}
+instead of simply returning null. Similarly, <code>GetMethodID()</code> and
+<code>GetStaticMethodID()</code> now properly throw {@link java.lang.NoSuchMethodError}.
+This can lead to CheckJNI failures because of the unhandled exceptions or the
+exceptions being thrown to Java callers of native code. This makes it
+particularly important to test ART-compatible apps with CheckJNI mode.</p>
+
+<p>ART expects users of the JNI <code>CallNonvirtual...Method()</code> methods
+(such as <code>CallNonvirtualVoidMethod()</code>) to use the method's declaring
+class, not a subclass, as required by the JNI specification.</p>
+
+<h2 id="Stack_Size">Preventing Stack Size Issues</h2>
+
+<p>Dalvik had separate stacks for native and Java code, with a default Java
+stack size of 32KB and a default native stack size of 1MB. ART has a unified
+stack for better locality. Ordinarily, the ART {@link java.lang.Thread} stack
+size should be approximately the same as for Dalvik. However, if you explicitly
+set stack sizes, you may need to revisit those values for apps running in
+ART.</p>
+
+<ul>
+
+  <li>In Java, review calls to the {@link
+  java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.Runnable,
+  java.lang.String, long) Thread} constructor that specify an explicit stack
+  size. For example, you will need to increase the size if {@link
+  java.lang.StackOverflowError} occurs.</li>
+
+  <li>In C/C++, review use of <code>pthread_attr_setstack()</code> and
+  <code>pthread_attr_setstacksize()</code> for threads that also run Java code via
+  JNI. Here is an example of the error logged when an app attempts to call JNI
+  <code>AttachCurrentThread()</code> when the pthread size is too small:
+
+<pre class="no-pretty-print">F/art: art/runtime/thread.cc:435]
+    Attempt to attach a thread with a too-small stack (16384 bytes)</pre>
+  </li>
+
+</ul>
+
+<h2 id="Object_Model_Changes">Object model changes</h2>
+
+<p>Dalvik incorrectly allowed subclasses to override package-private methods.
+ART issues a warning in such cases:</p>
+
+<pre class="no-pretty-print">
+Before Android 4.1, method void com.foo.Bar.quux()
+would have incorrectly overridden the package-private method in
+com.quux.Quux
+</pre>
+
+<p>If you intend to override a class's method in a different package, declare the
+method as <code>public</code> or <code>protected</code>.</p>
+
+<p>{@link java.lang.Object} now has private fields. Apps that reflect on fields
+in their class hierarchies should be careful not to attempt to look at the
+fields of {@link java.lang.Object}. For example, if you are iterating up a class
+hierarchy as part of a serialization framework, stop when
+
+<pre>Class.getSuperclass() == java.lang.Object.class</pre>
+
+instead of continuing until the method returns <code>null</code>.</p>
+
+<p>Proxy {@link
+java.lang.reflect.InvocationHandler#invoke(java.lang.Object,java.lang.reflect.Method,java.lang.Object[])
+InvocationHandler.invoke()} now receives <code>null</code> if there are no
+arguments instead of an empty array. This behavior was documented previously but
+not correctly handled in Dalvik. Previous versions of <a
+href="https://code.google.com/p/mockito/">Mockito</a> have difficulties with
+this, so use an updated Mockito version when testing with ART.</p>
+
+<h2 id="AOT_Fails">Fixing AOT Compilation Issues</h2>
+
+<p>ART's Ahead-Of-Time (AOT) Java compilation should work for all standard Java
+code. Compilation is performed by ART's
+<code>dex2oat</code> tool; if you encounter any issues related to
+<code>dex2oat</code> at install time, let us know (see <a
+href="#Reporting_Problems">Reporting Problems</a>) so we can fix them as quickly
+as possible. A couple of issues to note:</p>
+
+<ul>
+
+  <li>ART does tighter bytecode verification at install time than Dalvik does.
+  Code produced by the Android build tools should be fine. However, some
+  post-processing tools (especially tools that perform obfuscation) may produce
+  invalid files that are tolerated by Dalvik but rejected by ART. We have been
+  working with tool vendors to find and fix such issues. In many cases, getting
+  the latest versions of your tools and regenerating the DEX files can fix these
+  problems.</li>
+
+  <li>Some typical problems that are flagged by the ART verifier include:
+    <ul>
+      <li>invalid control flow</li>
+      <li>unbalanced <code>moniterenter</code>/<code>moniterexit</code></li>
+      <li>0-length parameter type list size</li>
+    </ul>
+  </li>
+
+  <li>Some apps have dependencies on the installed <code>.odex</code> file
+  format in <code>/system/framework</code>, <code>/data/dalvik-cache</code>, or
+  in {@link dalvik.system.DexClassLoader}’s optimized output directory. These
+  files are now ELF files and not an extended form of DEX files. While ART tries
+  to follow the same naming and locking rules as Dalvik, apps should not depend
+  on the file format; the format is subject to change without notice.</li>
+
+
+
+<h2 id="Reporting_Problems">Reporting Problems</h2>
+
+<p>If you run into any issues that aren’t due to app JNI issues, report
+them via the Android Open Source Project Issue Tracker at <a
+href="https://code.google.com/p/android/issues/list">https://code.google.com/p/android/issues/list</a>.
+Include an <code>"adb bugreport"</code> and a link to the app in the Google
+Play store if available. Otherwise, if possible, attach an APK that reproduces
+the issue. Note that issues (including attachments) are publicly
+visible.</p>
diff --git a/docs/html/guide/topics/ui/settings.jd b/docs/html/guide/topics/ui/settings.jd
index 1d36430..f454c4e 100644
--- a/docs/html/guide/topics/ui/settings.jd
+++ b/docs/html/guide/topics/ui/settings.jd
@@ -820,7 +820,8 @@
     public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType";
     ...
 
-    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+        String key) {
         if (key.equals(KEY_PREF_SYNC_CONN)) {
             Preference connectionPref = findPreference(key);
             // Set summary to be the user-description for the selected value
@@ -863,7 +864,40 @@
 }
 </pre>
 
+<p class="caution"><strong>Caution:</strong> When you call {@link
+android.content.SharedPreferences#registerOnSharedPreferenceChangeListener
+registerOnSharedPreferenceChangeListener()}, the preference manager does not
+currently store a strong reference to the listener. You must store a strong
+reference to the listener, or it will be susceptible to garbage collection. We
+recommend you keep a reference to the listener in the instance data of an object
+that will exist as long as you need the listener.</p>
 
+<p>For example, in the following code, the caller does not keep a reference to
+the listener. As a result, the listener will be subject to garbage collection,
+and it will fail at some indeterminate time in the future:</p>
+
+<pre>
+prefs.registerOnSharedPreferenceChangeListener(
+  // Bad! The listener is subject to garbage collection!
+  new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+});
+</pre>
+
+<p>Instead, store a reference to the listener in an instance data field of an
+object that will exist as long as the listener is needed:</p>
+
+<pre>
+SharedPreferences.OnSharedPreferenceChangeListener listener =
+    new SharedPreferences.OnSharedPreferenceChangeListener() {
+  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    // listener implementation
+  }
+};
+prefs.registerOnSharedPreferenceChangeListener(listener);
+</pre>
 
 <h2 id="NetworkUsage">Managing Network Usage</h2>
 
@@ -1142,13 +1176,15 @@
     final Parcelable superState = super.onSaveInstanceState();
     // Check whether this Preference is persistent (continually saved)
     if (isPersistent()) {
-        // No need to save instance state since it's persistent, use superclass state
+        // No need to save instance state since it's persistent,
+        // use superclass state
         return superState;
     }
 
     // Create instance of custom BaseSavedState
     final SavedState myState = new SavedState(superState);
-    // Set the state's value with the class member that holds current setting value
+    // Set the state's value with the class member that holds current
+    // setting value
     myState.value = mNewValue;
     return myState;
 }
diff --git a/docs/html/images/brand/Google_Play_Store_600.png b/docs/html/images/brand/Google_Play_Store_600.png
new file mode 100644
index 0000000..f748652
--- /dev/null
+++ b/docs/html/images/brand/Google_Play_Store_600.png
Binary files differ
diff --git a/docs/html/images/gp-ads-console.jpg b/docs/html/images/gp-ads-console.jpg
new file mode 100644
index 0000000..158e31d
--- /dev/null
+++ b/docs/html/images/gp-ads-console.jpg
Binary files differ
diff --git a/docs/html/images/gp-ads-linking2.jpg b/docs/html/images/gp-ads-linking2.jpg
new file mode 100644
index 0000000..0c2f731
--- /dev/null
+++ b/docs/html/images/gp-ads-linking2.jpg
Binary files differ
diff --git a/docs/html/images/gp-analytics.jpg b/docs/html/images/gp-analytics.jpg
new file mode 100644
index 0000000..e1a92c7
--- /dev/null
+++ b/docs/html/images/gp-analytics.jpg
Binary files differ
diff --git a/docs/html/images/training/volley-request.png b/docs/html/images/training/volley-request.png
new file mode 100644
index 0000000..85f0681
--- /dev/null
+++ b/docs/html/images/training/volley-request.png
Binary files differ
diff --git a/docs/html/jd_collections.js b/docs/html/jd_collections.js
index 8a4ac47..5ee01f3 100644
--- a/docs/html/jd_collections.js
+++ b/docs/html/jd_collections.js
@@ -49,7 +49,8 @@
       "distribute/users/your-listing.html",
       "distribute/users/build-buzz.html",
       "distribute/users/build-community.html",
-      "distribute/users/expand-to-new-markets.html"
+      "distribute/users/expand-to-new-markets.html",
+      "distribute/users/promote-with-ads.html"
     ]
   },
   "distribute/engagelanding": {
@@ -60,6 +61,7 @@
       "distribute/engage/easy-signin.html",
       "distribute/engage/deep-linking.html",
       "distribute/engage/game-services.html",
+      "distribute/engage/analytics.html",
       "distribute/engage/app-updates.html",
       "distribute/engage/community.html",
       "distribute/engage/video.html"
@@ -214,6 +216,13 @@
       "distribute/stories/localization.html"
     ]
   },
+  "distribute/users/promotewithads": {
+    "title": "",
+    "resources": [
+      "http://www.google.com/ads/admob/#subid=us-en-et-dac",
+      "distribute/essentials/optimizing-your-app.html"
+    ]
+  },
   "distribute/users/buildbuzz": {
     "title": "",
     "resources": [
@@ -394,6 +403,14 @@
       "http://play.google.com/about/developer-content-policy.html"
     ]
   },
+  "distribute/engage/analytics": {
+    "title": "",
+    "resources": [
+      "http://www.google.com/analytics/mobile/",
+      "http://android-developers.blogspot.com/2013/10/improved-app-insight-by-linking-google.html",
+      "https://developers.google.com/analytics/devguides/collection/android/"
+    ]
+  },
   "distribute/engage/widgets": {
     "title": "",
     "resources": [
diff --git a/docs/html/jd_extras.js b/docs/html/jd_extras.js
index f26b747b..d8db5bf 100644
--- a/docs/html/jd_extras.js
+++ b/docs/html/jd_extras.js
@@ -726,6 +726,21 @@
     "tags": [
       "#engagement",
     ],
+    "url": "http://www.google.com/analytics/mobile/",
+    "timestamp": 1383243492000,
+    "image": "http://www.google.com//analytics/images/heros/mobile-index.jpg",
+    "title": "Google Mobile App Analytics",
+    "summary": "Mobile App Analytics measures what matters most at all key stages: from first discovery and download to in-app purchases. ",
+    "keywords": ["analytics,user behavior"],
+    "type": "guide",
+    "titleFriendly": ""
+  },
+  {
+    "lang": "en",
+    "group": "",
+    "tags": [
+      "#engagement",
+    ],
     "url": "https://developers.google.com/app-indexing/",
     "timestamp": 1383243492000,
     "image": "https://developers.google.com/app-indexing/images/allthecooks_srp.png",
@@ -1101,4 +1116,17 @@
     "type": "Google+",
     "titleFriendly": ""
   },
+  {
+    "lang": "en",
+    "group": "",
+    "tags": ["analytics"],
+    "url": "https://developers.google.com/analytics/devguides/collection/android/",
+    "timestamp": null,
+    "image": "https://developers.google.com/analytics/images/home/gear-logo.png",
+    "title": "Google Mobile App Analytics SDK",
+    "summary": "The Google Analytics for Mobile Apps SDKs make it easy for you to implement Google Analytics in your mobile application.",
+    "keywords": ["analytics, user behavior"],
+    "type": "sdk",
+    "titleFriendly": ""
+  }
 ]); 
\ No newline at end of file
diff --git a/docs/html/sdk/exploring.jd b/docs/html/sdk/exploring.jd
index 7749060..b34c1cf 100644
--- a/docs/html/sdk/exploring.jd
+++ b/docs/html/sdk/exploring.jd
@@ -5,163 +5,6 @@
 @jd:body
 
 
-<p>The Android SDK is composed of modular packages that you can download separately using
-the Android SDK Manager. For example, when the SDK Tools are updated or a new version of
-the Android platform is released, you can use the SDK Manager to quickly download them to
-your environment. Simply follow the procedures described in <a
-href="{@docRoot}sdk/installing/adding-packages.html">Adding Platforms and Packages</a>.</p>
-
-<p>There are several different packages available for the Android SDK. The table below describes
-most of the available packages and where they're located once you download them.</p>
-
-
-<h2 id="Packages">Available Packages</h2>
-
-
-<table>
-  <tr><th>Package</th><th>Description</th><th>File Location</th></tr>
-  <tr>
-    <td><a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools</a></td>
-    <td>Contains tools for debugging and testing, plus other
-utilities that are required to develop an app. If you've just installed the SDK starter package,
-then you already have the latest version of this package. Make sure you keep this up to date.</td>
-    <td>{@code &lt;sdk>/tools/}</td></tr>
-  <tr><td>SDK Platform-tools</td>
-    <td>Contains platform-dependent tools for developing and debugging
-your application. These tools support the latest features of the Android platform and are typically
-updated only when a new platform becomes available. These tools are always backward compatible with
-older platforms, but you must be sure that you have the latest version of these tools when you
-install a new SDK platform.</td>
-    <td>{@code &lt;sdk>/platform-tools/}</td>
-  </tr>
-  
-  <tr>
-    <td>Documentation</td>
-    <td>An offline copy of the latest documentation for the Android
-platform APIs.</td>
-    <td>{@code &lt;sdk>/docs/}</td>
-  </tr>
-  <tr><td>SDK Platform</td>
-    <td>There's one SDK Platform available for each version of Android. It includes an {@code
-android.jar} file with a fully compliant Android library. In order to build an Android app, you must
-specify an SDK platform as your build target.</td>
-    <td>{@code &lt;sdk>/platforms/&lt;android-version>/}</td>
-  </tr>
-  <tr>
-    <td>System Images</td>
-    <td>Each platform version offers one or more different system images (such as for ARM
-and x86). The Android emulator requires a system image to operate. You should always test your
-app on the latest version of Android and using the emulator with the latest system image is a
-good way to do so.</td>
-    <td>{@code &lt;sdk>/platforms/&lt;android-version>/}</td>
-  </tr>
-  <tr>
-    <td>Sources for Android SDK</td>
-    <td>A copy of the Android platform source code that's useful for
-stepping through the code while debugging your app.</td>
-    <td>{@code &lt;sdk>/sources/}</td>
-  </tr>
-  <tr>
-    <td><a href="{@docRoot}tools/samples/index.html">Samples for SDK</a></td>
-    <td>A collection of sample apps that demonstrate a variety of the
-platform APIs. These are a great resource to browse Android app code. The API Demos app in
-particular provides a huge number of small demos you should explore.</td>
-    <td>{@code &lt;sdk>/platforms/&lt;android-version>/samples/}</td>
-  </tr>
-  <tr>
-    <td><a href="http://developers.google.com/android">Google APIs</a></td>
-    <td>An SDK add-on that provides both a platform you can use to develop an app
-using special Google APIs and a system image for the emulator so you can test your app using the
-Google APIs.</td>
-    <td>{@code &lt;sdk>/add-ons/}</td>
-  </tr>
-  
-  <tr>
-    <td><a href="{@docRoot}tools/support-library/index.html">Android Support</a></td>
-    <td>A static library you can include in your app sources in order to use powerful
-APIs that aren't available in the standard platform. For example, the support library
-contains versions of the {@link android.support.v4.app.Fragment} class that's compatible with
-Android 1.6 and higher (the class was originally introduced in Android 3.0) and the {@link
-android.support.v4.view.ViewPager} APIs that allow you to easily build a side-swipeable UI.</td>
-    <td>{@code &lt;sdk>/extras/android/support/}</td>
-  </tr>
-  <tr>
-    <td><a href="{@docRoot}google/play/billing/index.html">Google Play Billing</a></td>
-    <td>Provides the static libraries and samples that allow you to
-integrate billing services in your app with Google Play.</td>
-    <td>{@code &lt;sdk>/extras/google/}</td>
-  </tr>
-  <tr>
-    <td><a href="{@docRoot}google/play/licensing/index.html">Google Play Licensing</a></td>
-    <td>Provides the static libraries and samples that allow you to perform license verification for
-your app when distributing with Google Play.</td>
-    <td>{@code &lt;sdk>/extras/google/}</td>
-  </tr>
-</table>
-
-<p>The above table is not comprehensive and you can <a
-href="#AddingSites">add new sites</a> to download additional packages from third-parties.</p>
-
-<p>In some cases, an SDK package may require a specific minimum revision of
-another package or SDK tool. For example, there may be a dependency between the ADT Plugin for
-Eclipse and
-the SDK Tools package. When you install the SDK Tools
-package, you should also upgrade to the required version of ADT (if you
-are developing in Eclipse). In this case,  the major version number for your ADT plugin should
-always match the revision number of your SDK Tools (for example, ADT 8.x requires SDK Tools r8).
-</p>
-
-<p>The development tools will notify you with debug warnings if there is dependency that you need to
-address. The Android SDK Manager also enforces dependencies by requiring that you download any
-packages that are needed by those you have selected.</p>
-
-
-
-
-
-<h2 id="AddingSites">Adding New Sites</h2>
-
-<p>By default, <strong>Available Packages</strong> displays packages available from the
-<em>Android Repository</em> and <em>Third party Add-ons</em>. You can add other sites that host
-their own Android SDK add-ons, then download the SDK add-ons
-from those sites.</p>
-
-<p>For example, a mobile carrier or device manufacturer might offer additional
-API libraries that are supported by their own Android-powered devices. In order
-to develop using their libraries, you must install their Android SDK add-on, if it's not already
-available under <em>Third party Add-ons</em>. </p>
-
-<p>If a carrier or device manufacturer has hosted an SDK add-on repository file
-on their web site, follow these steps to add their site to the Android SDK
-Manager:</p>
-
-<ol>
-  <li>Select <strong>Available Packages</strong> in the left panel.</li>
-  <li>Click <strong>Add Add-on Site</strong> and enter the URL of the
-<code>repository.xml</code> file. Click <strong>OK</strong>.</li>
-</ol>
-<p>Any SDK packages available from the site will now be listed under a new item named
-<strong>User Add-ons</strong>.</p>
-
-
-
-
-<h2 id="troubleshooting">Troubleshooting</h2>
-
-<p><strong>Problems connecting to the SDK repository</strong></p>
-
-<p>If you are using the Android SDK Manager to download packages and are encountering
-connection problems, try connecting over http, rather than https. To switch the
-protocol used by the Android SDK Manager, follow these steps: </p>
-
-<ol>
-  <li>With the Android SDK Manager window open, select "Settings" in the
-  left pane. </li>
-  <li>On the right, in the "Misc" section, check the checkbox labeled "Force
-  https://... sources to be fetched using http://..." </li>
-  <li>Click <strong>Save &amp; Apply</strong>.</li>
-</ol>
-
 
 
 
diff --git a/docs/html/sdk/index.jd b/docs/html/sdk/index.jd
index e7f78e8..c6dbb90 100644
--- a/docs/html/sdk/index.jd
+++ b/docs/html/sdk/index.jd
@@ -206,12 +206,10 @@
 
 
 <div id="next-steps" style="display:none;position:absolute;width:inherit">
-  <p>Now that you've downloaded the Android SDK, you don't need to return here
-  for SDK updates. The SDK tools allow you to
-  install additional packages and future updates from the SDK Manager.</p>
-  <p>For instructions about setting up your Android SDK for the first time,
-  read <a id="next-link" href="{@docRoot}sdk/installing/bundle.html">Setting
-  Up the ADT Bundle</a>.</p>
+  <p>You're just a few steps away from building apps for Android!</p>
+  <p>In a moment, you'll be redirected to <a
+  id="next-link" href="{@docRoot}sdk/installing/index.html">Installing the
+  Android SDK</a>.</p>
 
 </div><!-- end next-steps -->
 
diff --git a/docs/html/sdk/installing/adding-packages.jd b/docs/html/sdk/installing/adding-packages.jd
index bba936e..c38c927 100644
--- a/docs/html/sdk/installing/adding-packages.jd
+++ b/docs/html/sdk/installing/adding-packages.jd
@@ -1,63 +1,222 @@
-page.title=Adding Platforms and Packages
+page.title=Adding SDK Packages
 
 @jd:body
 
+<style>
+ol.large {
+  margin-left:0;
+}
+ol.large > li {
+  list-style-position: inside;
+  list-style-type:none;
+  margin:30px 0 0 0;
+  padding:30px 20px;
+  background:#eee;
+}
+ol.large > li:nth-child(odd) {
+} 
+ol.large > li:before {
+  display:inline;
+  left:-40px;
+  float:left;
+  width:20px;
+  font-size:20px;
+  line-height:20px;
+}
+ol.large > li > h2 {
+  font-size:20px;
+  line-height:20px;
+  padding:0 0 0 20px;
+  margin:0 0 20px 0;
+  display:inline-block;
+  font-weight:normal;
+}
+ol.large > li:nth-child(1):before {
+  content:"1. ";
+}
+ol.large > li:nth-child(2):before {
+  content:"2. ";
+}
+ol.large > li:nth-child(3):before {
+  content:"3. ";
+}
+ol.large > li:nth-child(4):before {
+  content:"4. ";
+}
+ol.large > li:nth-child(5):before {
+  content:"5. ";
+}
+ol.large > li:nth-child(6):before {
+  content:"6. ";
+}
+</style>
 
-<p>The Android SDK separates tools, platforms, and other components into packages you can
-  download using the Android SDK Manager. The original
-SDK package you've downloaded includes only the SDK Tools. To develop an Android app,
-you also need to download at least one Android platform and the latest SDK Platform-tools.</p>
 
-<ol>
-<li>Launch the SDK Manager.
-<p>If you've used the Windows installer to install the SDK tools, you should already have the
-Android SDK Manager open. Otherwise, you can launch the Android SDK Manager in one of the following
-ways:</p>
+<p>
+By default, the Android SDK does not include everything you need to start developing.
+The SDK separates tools, platforms, and other components into packages you can
+download as needed using the
+<a href="{@docRoot}tools/help/sdk-manager.html">Android SDK Manager</a>.
+So before you can start, there are a few packages you should add to your Android SDK.</p>
+
+<p>To start adding packages, launch the Android SDK Manager in one of the following ways:</p>
 <ul>
-  <li>On Windows, double-click the <code>SDK Manager.exe</code> file at the root of the Android
-SDK directory.</li>
-  <li>On Mac or Linux, open a terminal and navigate to the <code>tools/</code> directory in the
-Android SDK, then execute <code>android sdk</code>.</li>
+  <li>In Eclipse or Android Studio, click <strong>SDK Manager</strong>
+<img src="{@docRoot}images/tools/sdk-manager-studio.png"
+style="vertical-align:bottom;margin:0;height:17px" /> in the toolbar.</li>
+  <li>If you're not using Eclipse or Android Studio:
+    <ul>
+      <li>Windows: Double-click the <code>SDK Manager.exe</code> file at the root of the Android
+  SDK directory.</li>
+      <li>Mac/Linux: Open a terminal and navigate to the <code>tools/</code> directory in the
+  Android SDK, then execute <code>android sdk</code>.</li>
+    </ul>
+  </li>
 </ul>
+
+<ol class="large">
+<li>
+  <h2 class="norule">Get the latest SDK tools</h2>
+
+<img src="/images/sdk_manager_packages.png" alt="" width="350" style="float:right;margin-left:20px" />
+
+  <p>As a minimum when setting up the Android SDK,
+  you should download the latest tools and Android platform:</p>
+  <ol>
+   <li>Open the Tools directory and select:
+     <ul>
+       <li><strong>Android SDK Tools</strong></li>
+       <li><strong>Android SDK Platform-tools</strong></li>
+       <li><strong>Android SDK Build-tools</strong></li>
+     </ul>
+   </li>
+   <li>Open the first Android X.X folder (the latest version) and select:
+     <ul>
+      <li><strong>SDK Platform</strong></li>
+      <li>A system image for the emulator, such as <br>
+      <strong>ARM EABI v7a System Image</strong></li>
+     </ul>
+   </li>
+   <li>Click <strong>Install</strong>.</li>
+  </ol>
 </li>
 
-<li>The SDK Manager shows all the SDK packages available for you to add to your Android SDK.
-As a minimum configuration for your SDK, we recommend you install the following:
-<ul>
- <li>The latest Tools packages (check the <strong>Tools</strong> folder).</li>
- <li>The latest version of Android (check the first <strong>Android</strong> folder).</li>
- <li>The Android Support Library (open the <strong>Extras</strong> folder and check
-  <strong>Android Support Library</strong>).</li>
-</ul>
+<li>
+  <h2 class="norule">Get the support library for additional APIs</h2>
 
-<p>Once you've chosen your packages, click <strong>Install</strong>. The Android SDK Manager
-installs the selected packages into your Android SDK environment.</li>
+  <div class="sidebox">
+    <h3>Why use the support library?</h3>
+
+    <p>The support library is required for:</p>
+    <ul>
+      <li><a href="{@docRoot}wear/index.html">Android Wear</a></li>
+      <li><a href="{@docRoot}tv/index.html">Android TV</a></li>
+      <li><a href="{@docRoot}google/play-services/cast.html">Google Cast</a></li>
+    </ul>
+
+    <p>The support library also provides these popular APIs:</p>
+    <ul>
+      <li><a href="{@docRoot}reference/android/support/v4/widget/DrawerLayout.html">Navigation
+      drawer</a></li>
+      <li><a href="{@docRoot}reference/android/support/v4/view/ViewPager.html">Swipe views</a></li>
+      <li><a href="{@docRoot}reference/android/support/v7/app/ActionBar.html">Backward-compatible
+      action bar</a></li>
+    </ul>
+  </div>
+
+  <p>The <a href="{@docRoot}tools/support-library/features.html">Android Support Library</a>
+  provides an extended set of APIs that are compatible with most versions of Android.</p>
+
+  <p>To download the support library:</p>
+  <ol>
+    <li>Open the <strong>Extras</strong> directory and select:
+     <ul>
+       <li><strong>Android Support Repository</strong></li>
+       <li><strong>Android Support Library</strong></li>
+     </ul>
+    </li>
+    <li>Click <strong>Install</strong>.</li>
+  </ol>
+
+  <p>&nbsp;</p>
+  <p>&nbsp;</p>
+
+</li>
+
+
+<li>
+  <h2 class="norule">Get Google Play services for even more APIs</h2>
+
+  <div class="sidebox">
+    <h3>Why use Google Play services?</h3>
+
+    <p>The Google Play services APIs provide a variety of features and services for your Android
+    apps, such as:</p>
+    <ul>
+      <li><a href="{@docRoot}google/play-services/plus.html">User authentication</a></li>
+      <li><a href="{@docRoot}google/play-services/maps.html">Google Maps</a></li>
+      <li><a href="{@docRoot}google/play-services/cast.html">Google Cast</a></li>
+      <li><a href="{@docRoot}google/play-services/games.html">Games achievements and
+      leaderboards</a></li>
+      <li><a href="{@docRoot}google/play-services/index.html">And much more</a></li>
+    </ul>
+  </div>
+
+  <p>To develop with Google APIs, you need the Google Play services package:</p>
+  <ol>
+    <li>Open the <strong>Extras</strong> directory and select:
+     <ul>
+       <li><strong>Google Repository</strong></li>
+       <li><strong>Google Play services</strong></li>
+     </ul>
+    </li>
+    <li>Click <strong>Install</strong>.</li>
+  </ol>
+
+  <p class="note"><strong>Note:</strong> Google Play services APIs are not available on all
+  Android-powered devices, but are available on all devices with Google Play Store. To use these
+  APIs in the Android emulator, you must also install the the <strong>Google APIs</strong>
+  system image from the latest Android X.X directory in the SDK Manager.</p>
+</li>
+
+
+
+<li>
+  <h2 class="norule">Build something!</h2>
+
+<p>With the above packages now in your Android SDK, you're ready to build apps
+for Android. As new tools and other APIs become available, simply launch the SDK Manager
+  to download the new packages for your SDK.</p>
+
+<p>Here are a few options for how you should proceed:</p>
+
+<div class="cols" style="padding:10px 0">
+<div class="col-4">
+<h3>Get started</h3>
+<p>If you're new to Android development, learn the basics of Android apps by following
+the guide to <strong><a href="{@docRoot}training/basics/firstapp/index.html"
+>Building Your First App</a></strong>.</p>
+
+</div>
+<div class="col-4 box">
+<h3>Build for wearables</h3>
+<p>If you're ready to start building apps for Android wearables, see the guide to
+<strong><a href="{@docRoot}wear/preview/start.html">Building Apps for Android Wear</a></strong>.</p>
+
+</div>
+<div class="col-4 box">
+<h3>Use Google APIs</h3>
+<p>To start using Google APIs, such as Maps or
+Play Game services, see the guide to
+<strong><a href="{@docRoot}google/auth/api-client.html">Accessing Google Play Services
+APIs</a></strong>.</p>
+
+</div>
+</div><!-- end cols -->
+
+
+</li>
+
 </ol>
 
-<p>With these packages installed, you're ready to start developing.
-To get started, read <a href="{@docRoot}training/basics/firstapp/index.html"
->Building Your First App</a>.</p>
-
-<img src="/images/sdk_manager_packages.png" alt="" height="396" />
-<p class="img-caption"><strong>Figure 1.</strong> The Android SDK Manager shows the
-SDK packages that are available, already installed, or for which an update is available.</p>
-
-
-
-<h3>Additional information</h3>
-
-<ul>
-  <li>For more information about using the SDK Manager and some of the available packages,
-see the <a href="{@docRoot}tools/help/sdk-manager.html">SDK Manager</a> document.</li>
-  <li>This web site provides all information you need to develop Android apps, including <a
-href="{@docRoot}design/index.html">design guidelines</a>,
-<a href="{@docRoot}training/index.html">developer training</a>, <a
-href="{@docRoot}reference/packages.html">API reference</a>, and information
-about how you can <a href="{@docRoot}distribute/index.html">distribute your app</a>. We recommend
-you begin by reading <a href="{@docRoot}training/basics/firstapp/index.html"
->Building Your First App</a>.</li>
-  <li>For additional resources about developing and distributing your app, see the
-<a href="{@docRoot}support.html">Developer Support Resources</a>.</li>
-</ul>
-
 
diff --git a/docs/html/sdk/installing/bundle.jd b/docs/html/sdk/installing/bundle.jd
index 1f7da55..22bdd11 100644
--- a/docs/html/sdk/installing/bundle.jd
+++ b/docs/html/sdk/installing/bundle.jd
@@ -1,45 +1,3 @@
 page.title=Setting Up the ADT Bundle
 
 @jd:body
-
-
-<p>The ADT Bundle provides everything you need to start developing apps, including
-a version of the Eclipse IDE with built-in <b>ADT (Android Developer Tools)</b> to
-streamline your Android app development.
-If you haven't already, go download the <a href="{@docRoot}sdk/index.html"
->Android ADT Bundle</a>. (If you downloaded the SDK Tools only, for use with an
-existing IDE, you should instead read
-<a href="{@docRoot}sdk/installing/index.html">Setting Up an Existing IDE</a>.)</p>
-
-<h3>Install the SDK and Eclipse IDE</h3>
-<ol>
-<li>Unpack the ZIP file
-(named {@code adt-bundle-&lt;os_platform>.zip}) and save it to an appropriate location,
-such as a "Development" directory in your home directory.</li>
-<li>Open the {@code adt-bundle-&lt;os_platform>/eclipse/} directory and launch
-<strong>eclipse</strong>.</li>
-</ol>
-
-<p>That's it! The IDE is already loaded with the Android Developer Tools plugin and
-the SDK is ready to go. To start developing, read <a href="{@docRoot}training/basics/firstapp/index.html"
->Building Your First App</a>.</p>
-
-<p class="caution"><strong>Caution:</strong> Do not move any of the files or directories
-from the {@code adt-bundle-&lt;os_platform>} directory. If you move the {@code eclipse}
-or {@code sdk} directory, ADT will not be able to locate the SDK and you'll
-need to manually update the ADT preferences.</p>
-
-<h3>Additional information</h3>
-
-<p>As you continue developing apps, you may need to install additional versions
-of Android for the emulator and other packages such as the library for
-Google Play In-app Billing. To install more packages, use
-the <a href="{@docRoot}tools/help/sdk-manager.html">SDK Manager</a>.</p>
-
-<p>Everything you need to develop Android apps is on this web site, including <a
-href="{@docRoot}design/index.html">design guidelines</a>,
-<a href="{@docRoot}training/index.html">developer training</a>, <a
-href="{@docRoot}reference/packages.html">API reference</a>, and information
-about how you can <a href="{@docRoot}distribute/index.html">distribute your app</a>.
-For additional resources about developing and distributing your app, see the
-<a href="{@docRoot}support.html">Developer Support Resources</a>.</p>
\ No newline at end of file
diff --git a/docs/html/sdk/installing/index.jd b/docs/html/sdk/installing/index.jd
index 6b63ba7..b6929bd 100644
--- a/docs/html/sdk/installing/index.jd
+++ b/docs/html/sdk/installing/index.jd
@@ -1,19 +1,213 @@
-page.title=Setting Up an Existing IDE
+page.title=Installing the Android SDK
 
 @jd:body
 
-
-<p>You should have already downloaded the <a href="{@docRoot}sdk/index.html#ExistingIDE"
->Android SDK Tools</a>. (If you downloaded the ADT Bundle, you should instead read
-<a href="{@docRoot}sdk/installing/bundle.html">Setting Up the ADT Bundle</a>.)</p>
-
-<p>The SDK Tools package is not the complete SDK environment. It includes only the core SDK tools, which you can
-use to download the rest of the SDK packages (such as the latest system image).</p>
+<style>
+.paging-links {
+  margin:0 0 80px;
+}
+.paging-links .next-page-link {
+  right:initial;
+}
+.procedure-box {
+  padding:20px 20px 5px;
+  margin-bottom:1em;
+  background:#eee;
+}
+</style>
 
 
-<div id="win" class="docs" style="display:none">
 
-<h3>Getting started on Windows</h3>
+<!-- ###################    ADT BUNDLE     ####################### -->
+<div id="adt" heading="Installing the Eclipse ADT Bundle" style="display:none">
+
+
+<p>The Eclipse ADT Bundle provides everything you need to start developing apps, including
+the Android SDK tools and a version of the Eclipse IDE with built-in ADT
+(Android Developer Tools) to streamline your Android app development.</p>
+
+<p>If you didn't download the Eclipse ADT bundle, go <a href="{@docRoot}sdk/index.html"
+><b>download the Eclipse ADT bundle now</b></a>, or switch to the
+<a href="{@docRoot}sdk/installing/index.html?pkg=studio">Android Studio
+install</a> or <a href="{@docRoot}sdk/installing/index.html?pkg=tools">stand-alone SDK Tools
+install</a> instructions</i>.</p>
+
+<div class="procedure-box">
+<p><b>To set up the ADT Bundle:</b></p>
+<ol>
+<li>Unpack the ZIP file
+(named {@code adt-bundle-&lt;os_platform>.zip}) and save it to an appropriate location,
+such as a "Development" directory in your home directory.</li>
+<li>Open the {@code adt-bundle-&lt;os_platform>/eclipse/} directory and launch
+<strong>Eclipse</strong>.</li>
+</ol>
+
+<p class="caution"><strong>Caution:</strong> Do not move any of the files or directories
+from the {@code adt-bundle-&lt;os_platform>} directory. If you move the {@code eclipse/}
+or {@code sdk/} directory, ADT will not be able to locate the SDK and you'll
+need to manually update the ADT preferences.</p>
+</div>
+
+<p>Eclipse with ADT is now ready and loaded with the Android developer tools, but there are still
+a couple packages you should add to make your Android SDK complete.</p>
+
+<p class="paging-links">
+<a href="{@docRoot}sdk/installing/adding-packages.html" class="next-page-link">
+Continue: Adding SDK Packages</a></p>
+
+
+</div>
+<!-- ################    END ADT BUNDLE    ##################### -->
+
+
+
+
+
+
+<!-- ################    STUDIO    ##################### -->
+<div id="studio" heading="Installing Android Studio" style="display:none">
+
+<p>Android Studio provides everything you need to start developing apps, including
+the Android SDK tools and the Android Studio IDE (powered by IntelliJ) to
+streamline your Android app development.</p>
+
+<p>If you didn't download Android Studio, go <a href="{@docRoot}sdk/installing/studio.html"
+><b>download Android Studio now</b></a>, or switch to the
+<a href="{@docRoot}sdk/installing/index.html?pkg=adt">Eclipse ADT
+install</a> or <a href="{@docRoot}sdk/installing/index.html?pkg=tools">stand-alone SDK Tools
+install</a> instructions.</p>
+
+
+<p>Before you set up Android Studio, be sure you have installed
+JDK 6 or greater (the JRE alone is not sufficient). To check if you
+have JDK installed (and which version), open a terminal and type <code>javac -version</code>.
+If the JDK is not available or the version is lower than 6,
+<a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html" class="external-link"
+>go download JDK</a>.</p>
+
+
+<div class="procedure-box">
+
+<p id="instructions-toggle"
+style="float:right;font-size:13px"><a href='' onclick='showAll();return false;'
+>[ Show instructions for all platforms ]</a></p>
+
+<div class="win docs" style="display:none">
+
+<p><b>To set up Android Studio on Windows:</b></p>
+  <ol>
+    <li>Launch the downloaded EXE file, {@code android-studio-bundle-&lt;version&gt;.exe}.</li>
+    <li>Follow the setup wizard to install Android Studio.
+
+    <p>On some Windows systems, the launcher script does not find where Java is installed.
+      If you encounter this problem,
+      you need to set an environment variable indicating the correct location.</p>
+      <p>Select <strong>Start menu > Computer > System Properties >
+      Advanced System Properties</strong>. Then open <strong>Advanced tab > Environment
+      Variables</strong> and add a new system variable <code>JAVA_HOME</code> that points to
+      your JDK folder, for example <code>C:\Program Files\Java\jdk1.7.0_21</code>.</p>
+    </p>
+    </li>
+
+  </ol>
+
+
+<p>The individual tools and
+other SDK packages are saved within the Android Studio application directory.
+To access the tools directly, use a terminal to navigate into the application and locate
+the {@code sdk/} directory. For example:</p>
+<p><code>\Users\&lt;user&gt;\AppData\Local\Android\android-studio\sdk\</code></p>
+
+
+
+</div><!-- end windows -->
+
+
+<div class="mac docs" style="display:none">
+
+<p><b>To set up Android Studio on Mac OSX:</b></p>
+  <ol>
+    <li>Open the downloaded DMG file, {@code android-studio-bundle-&lt;version&gt;.dmg}.</li>
+    <li>Drag and drop Android Studio into the Applications folder.
+      <p>
+      Depending on your security settings, when you attempt to open Android Studio, you might
+      see a warning that says the package is damaged and should be moved to the trash. If this
+      happens, go to <strong>System Preferences > Security &amp; Privacy</strong> and under
+      <strong>Allow applications downloaded from</strong>, select <strong>Anywhere</strong>.
+      Then open Android Studio again.</p>
+    </li>
+  </ol>
+
+<p>The individual tools and
+other SDK packages are saved within the Android Studio application directory.
+To access the tools directly, use a terminal to navigate into the application and locate
+the {@code sdk/} directory. For example:</p>
+<p><code>/Applications/Android\ Studio.app/sdk/</code></p>
+
+
+</div><!-- end mac -->
+
+
+<div class="linux docs" style="display:none">
+
+<p><b>To set up Android Studio on Linux:</b></p>
+
+  <ol>
+    <li>Unpack the downloaded Tar file, {@code android-studio-bundle-&lt;version&gt;.tgz}, into an appropriate
+    location for your applications.
+    <li>To launch Android Studio, navigate to the {@code android-studio/bin/} directory
+    in a terminal and execute {@code studio.sh}.
+      <p>You may want to add {@code android-studio/bin/} to your PATH environmental
+      variable so that you can start Android Studio from any directory.</p>
+    </li>
+  </ol>
+
+</div><!-- end linux -->
+</div><!-- end procedure box -->
+
+<p>Android Studio is now ready and loaded with the Android developer tools, but there are still a
+couple packages you should add to make your Android SDK complete.</p>
+
+<p class="paging-links">
+<a href="{@docRoot}sdk/installing/adding-packages.html" class="next-page-link">
+Continue: Adding SDK Packages</a></p>
+
+
+</div>
+<!-- ################    END STUDIO    ##################### -->
+
+
+
+
+
+
+
+
+
+<!-- ################    JUST SDK TOOLS    ##################### -->
+<div id="tools" heading="Installing the Stand-alone SDK Tools" style="display:none">
+
+
+<p>The stand-alone SDK Tools package does not include a complete Android development environment.
+It includes only the core SDK tools, which you can access from a command line or with a plugin
+for your favorite IDE (if available).</p>
+
+<p>If you didn't download the SDK tools, go <a href="{@docRoot}sdk/index.html"
+><b>download the SDK now</b></a>,
+or switch to the <a href="{@docRoot}sdk/installing/index.html?pkg=adt">Eclipse ADT
+install</a> or <a href="{@docRoot}sdk/installing/index.html?pkg=studio">Android Studio
+install</a> instructions.</p>
+
+
+<div class="procedure-box">
+<p id="instructions-toggle"
+style="float:right;font-size:13px"><a href='' onclick='showAll();return false;'
+>[ Show instructions for all platforms ]</a></p>
+
+<div class="win docs" style="display:none">
+
+<p><b>To get started on Windows:</b></p>
+
 <p>Your download package is an executable file that starts an installer. The installer checks your machine
   for required tools, such as the proper Java SE Development Kit (JDK) and installs it if necessary.
   The installer then saves the Android SDK Tools into a default location (or you can specify the location).</p>
@@ -21,15 +215,9 @@
 <ol>
 <li>Double-click the executable ({@code .exe} file) to start the install.</li>
 <li>Make a note of the name and location in which it saves the SDK on your system&mdash;you will need to
-refer to the SDK directory later, when setting up the ADT plugin and when using
+refer to the SDK directory later when using
 the SDK tools from the command line.</li>
-<li>Once the installation completes, the installer offers to start the Android SDK Manager.
-If you'll be using Eclipse, <strong>do not</strong> start the Android SDK Manager,
-and instead move on to <a href="{@docRoot}sdk/installing/installing-adt.html"
->Installing the Eclipse Plugin</a>.
-<p>If you're using a different IDE,
-start the SDK Manager and read <a href="{@docRoot}sdk/installing/adding-packages.html"
->Adding Platforms and Packages</a>.</p>
+<li>Once the installation completes, the installer starts the Android SDK Manager.
 </li>
 </ol>
 
@@ -37,51 +225,37 @@
 
 
 
-<div id="mac" class="docs" style="display:none">
+<div class="mac docs" style="display:none">
 
-<h3>Getting started on Mac</h3>
+<p><b>To get started on Mac OSX:</b></p>
 
-<ol>
-<li>Unpack the ZIP file you've downloaded. By default, it's unpacked
+<p>Unpack the ZIP file you've downloaded. By default, it's unpacked
 into a directory named <code>android-sdk-mac_x86</code>. Move it to an appropriate location on your machine,
-such as a "Development" directory in your home directory.
+such as a "Development" directory in your home directory.</p>
 
 <p>Make a note of the name and location of the SDK directory on your system&mdash;you will need to
-refer to the SDK directory later, when setting up the ADT plugin and when using
+refer to the SDK directory later when using
 the SDK tools from the command line.</p>
-</li>
-<li>If you're using Eclipse, move on to <a href="{@docRoot}sdk/installing/installing-adt.html"
->Installing the Eclipse Plugin</a>. Otherwise, if you're using a different IDE,
-read <a href="{@docRoot}sdk/installing/adding-packages.html"
->Adding Platforms and Packages</a>.</li>
-</ol>
 
 </div>
 
 
 
 
-<div id="linux" class="docs" style="display:none">
+<div class="linux docs" style="display:none">
 
-<h3>Getting started on Linux</h3>
+<p><b>To get started on Linux:</b></p>
 
-<ol>
-<li>Unpack the {@code .tgz} file you've downloaded. By default, the SDK files are unpacked
+<p>Unpack the {@code .tgz} file you've downloaded. By default, the SDK files are unpacked
 into a directory named <code>android-sdk-linux_x86</code>. Move it to an appropriate location on your machine,
-such as a "Development" directory in your home directory.
+such as a "Development" directory in your home directory.</p>
 
 <p>Make a note of the name and location of the SDK directory on your system&mdash;you will need to
-refer to the SDK directory later, when setting up the ADT plugin and when using
+refer to the SDK directory later when using
 the SDK tools from the command line.</p>
-</li>
-<li>If you're using Eclipse, move on to <a href="{@docRoot}sdk/installing/installing-adt.html"
->Installing the Eclipse Plugin</a>. Otherwise, if you're using a different IDE,
-read <a href="{@docRoot}sdk/installing/adding-packages.html"
->Adding Platforms and Packages</a>.</li>
-</ol>
 
 
-<h5 id="Troubleshooting"><a href='' class="expandable"
+<h5 id="Troubleshooting" style="margin-bottom:15px"><a href='' class="expandable"
   onclick="toggleExpandable(this,'#ubuntu-trouble');return false;"
   >Troubleshooting Ubuntu</a></h5>
 
@@ -122,38 +296,129 @@
 </div><!-- end ubuntu trouble -->
 
 
+</div><!-- end linux -->
+</div><!-- end procedure box -->
+
+
+<p>The Android SDK tools are now ready to begin developing apps, but there are still a
+couple packages you should add to make your Android SDK complete.</p>
+
+<p class="paging-links">
+<a href="{@docRoot}sdk/installing/adding-packages.html" class="next-page-link">
+Continue: Adding SDK Packages</a></p>
+
+
+</div>
+<!-- ################    END JUST TOOLS    ##################### -->
+
+
+
+
+
+<!-- ################    DEFAULT    ##################### -->
+<style>
+h3.large-link {
+  display:inline-block;
+  width:100%;
+  text-align:center;
+  border:1px solid #258aaf;
+  padding:10px 0;
+  color:inherit;
+}
+</style>
+
+<div id="default" style="display:none">
+
+<p>If you haven't already done so, <b><a href="{@docRoot}sdk/index.html">click here to download
+the Android SDK</a></b>. </p>
+
+<p>Otherwise, select which SDK package you want to install:</p>
+
+<div class="cols" style="margin-bottom:60px">
+<div class="col-4">
+<a href="{@docRoot}sdk/installing/index.html?pkg=adt">
+<h3 class="large-link">Eclipse ADT</h3>
+</a>
 </div>
 
-<p style="margin-top:2em;"><a href='' onclick='showAll();return false;'>Information for other platforms</a></p>
+<div class="col-4">
+<a href="{@docRoot}sdk/installing/index.html?pkg=studio">
+<h3 class="large-link">Android Studio</h3></a>
+</div>
+
+<div class="col-4">
+<a href="{@docRoot}sdk/installing/index.html?pkg=tools">
+<h3 class="large-link">Stand-alone SDK Tools</h3></a>
+</div>
+</div>
+
+
+</div>
+<!-- ################    END DEFAULT    ##################### -->
+
+
+
+
+
 
 <script>
-  var $osDocs;
-  if (navigator.appVersion.indexOf("Win")!=-1) {
-    $osDocs = $('#win');
-  } else if (navigator.appVersion.indexOf("Mac")!=-1) {
-    $osDocs = $('#mac');
-  } else if (navigator.appVersion.indexOf("Linux")!=-1) {
-    $osDocs = $('#linux');
-  }
 
-  if ($osDocs.length) {
-    // reveal only the docs for this OS
-    $osDocs.show();
-  } else {
-    // not running a compatible OS, so just show all the docs
-    $('.docs').show();
-  }
+// Show proper instructions based on downloaded SDK package
+var package = getUrlParam("pkg");
+if (package == "tools") {
+  // Show the SDK Tools (other IDE) instructions
+  $("h1").text($("#tools").attr('heading'));
+  $("#tools").show();
+} else if (package == "adt") {
+  // Show the ADT instructions
+  $("h1").text($("#adt").attr('heading'));
+  $("#adt").show();
+} else if (package == "studio") {
+  // Show the Android Studio instructions
+  $("h1").text($("#studio").attr('heading'));
+  $("#studio").show();
+} else {
+  // Show the default page content so user can select their setup
+  $("#default").show();
+}
 
-  function showAll() {
-    $('.docs').each(function() {
-      if (!$(this).is(':visible')) {
-        console.log('show')
-        $(this).show();
-      } else {
-        console.log('hide')
-        $(this).hide();
-        $osDocs.show();
-      }
-    });
+// Show the proper instructions based on machine OS
+var $osDocs;
+if (navigator.appVersion.indexOf("Win")!=-1) {
+  $osDocs = $('.win');
+} else if (navigator.appVersion.indexOf("Mac")!=-1) {
+  $osDocs = $('.mac');
+} else if (navigator.appVersion.indexOf("Linux")!=-1) {
+  $osDocs = $('.linux');
+}
+
+if ($osDocs.length) {
+  // reveal only the docs for this OS
+  $osDocs.show();
+} else {
+  // not running a compatible OS, so just show all the docs
+  $('.docs').show();
+}
+
+
+/* Shows all the machine OS instructions */
+function showAll() {
+  $('.docs').show();
+  $("#instructions-toggle").hide();
+}
+
+/* Returns the value for the given URL parameter */
+function getUrlParam(param) {
+  var url = window.location.search.substring(1);
+  var variables = url.split('&');
+  for (var i = 0; i < variables.length; i++) {
+    var paramName = variables[i].split('=');
+    if (escape(paramName[0]) == param) {
+      return escape(paramName[1]);
+    }
   }
+}
+
+
+
 </script>
diff --git a/docs/html/sdk/installing/installing-adt.jd b/docs/html/sdk/installing/installing-adt.jd
index 7bf366c..b23212d 100644
--- a/docs/html/sdk/installing/installing-adt.jd
+++ b/docs/html/sdk/installing/installing-adt.jd
@@ -15,19 +15,16 @@
 UI, debug your app, and export signed (or unsigned) app packages (APKs) for distribution.
 </p>
 
-<p>If you need to install Eclipse, you can download it from <a href=
-"http://www.eclipse.org/downloads/">eclipse.org/downloads/</a>.</p>
-
-
-<p class="note"><strong>Note:</strong> If you prefer to work in a different IDE, you do not need to
-install Eclipse or ADT. Instead, you can directly use the SDK tools to build and
-debug your application.</p>
+<p class="note"><strong>Note:</strong> You should install the ADT plugin
+only if you already have an Eclipse installation that you want to continue using. If you do not
+have Eclipse installed, you should instead <b><a href="{@docRoot}sdk/index.html">install
+the complete Android SDK</a></b>, which includes the latest IDE for Android developers.</p>
 
 
 
 <h2 id="Download">Download the ADT Plugin</h2>
 
-
+<p>To add the ADT plugin to Eclipse:</p>
 <ol>
     <li>Start Eclipse, then select <strong>Help</strong> &gt; <strong>Install New
 Software</strong>.</li>
diff --git a/docs/html/sdk/installing/studio.jd b/docs/html/sdk/installing/studio.jd
index a2c32f0..7127ba6 100644
--- a/docs/html/sdk/installing/studio.jd
+++ b/docs/html/sdk/installing/studio.jd
@@ -7,7 +7,7 @@
 
 <div style="position:relative;min-height:660px;">
 
-<h3 style="color:#f80">EARLY ACCESS PREVIEW</h3>
+<h3 style="color:#f80">BETA RELEASE</h3>
 
 <div id="tos" style="position:absolute;display:none;width:inherit;">
 <div class="col-13" style="margin:0;">&nbsp;</div><!-- provides top margin for content -->
@@ -183,8 +183,6 @@
 
 
 
-
-
 <div id="main">
 
 <div class="figure" style="width:400px;margin-top:-20px">
@@ -298,7 +296,7 @@
 Check for updates</strong>) to see whether an update is available.</p>
 
 <p>If an update is not available,
-follow the <a href="#Installing">installation instructions</a> below and replace your existing
+click the button above to download and replace your existing
 installation.</p>
 
 <div class="caution">
@@ -311,98 +309,6 @@
 the Android SDK Manager.</p>
 </div>
 
-
-<h2 id="Installing">Installing Android Studio</h2>
-<p>Android Studio requires JDK 6 or greater (JRE alone is not sufficient). To check if you
-have JDK installed (and which version), open a terminal and type <code>javac -version</code>.
-If JDK is not available or the version is lower than 6,
-<a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html">download
-JDK from here</a>.</p>
-<p>To install Android Studio:</p>
-<ol>
-<li>Download the <strong>Android Studio</strong> package from above.</li>
-<li>Install Android Studio and the SDK tools:
-  <p><b>Windows:</b></p>
-  <ol>
-    <li>Launch the downloaded EXE file, {@code android-studio-bundle-&lt;version&gt;.exe}.</li>
-    <li>Follow the setup wizard to install Android Studio.
-
-    <div class="caution"><p><strong>Known issue:</strong>
-      On some Windows systems, the launcher script does not find where Java is installed.
-      If you encounter this problem,
-      you need to set an environment variable indicating the correct location.</p>
-      <p>Select <strong>Start menu > Computer > System Properties >
-      Advanced System Properties</strong>. Then open <strong>Advanced tab > Environment
-      Variables</strong> and add a new system variable <code>JAVA_HOME</code> that points to
-      your JDK folder, for example <code>C:\Program Files\Java\jdk1.7.0_21</code>.</p>
-    </div>
-    </li>
-
-  </ol>
-  <p><b>Mac OS X:</b></p>
-  <ol>
-    <li>Open the downloaded DMG file, {@code android-studio-bundle-&lt;version&gt;.dmg}.</li>
-    <li>Drag and drop Android Studio into the Applications folder.
-
-    <div class="caution"><p><strong>Known issue:</strong>
-      Depending on your security settings, when you attempt to open Android Studio, you might
-      see a warning that says the package is damaged and should be moved to the trash. If this
-      happens, go to <strong>System Preferences > Security &amp; Privacy</strong> and under
-      <strong>Allow applications downloaded from</strong>, select <strong>Anywhere</strong>.
-      Then open Android Studio again.</p>
-    </div>
-    </li>
-
-  </ol>
-  <p><b>Linux:</b></p>
-  <ol>
-    <li>Unpack the downloaded Tar file, {@code android-studio-bundle-&lt;version&gt;.tgz}, into an appropriate
-    location for your applications.
-    <li>To launch Android Studio, navigate to the {@code android-studio/bin/} directory
-    in a terminal and execute {@code studio.sh}.
-      <p>You may want to add {@code android-studio/bin/} to your PATH environmental
-      variable so that you can start Android Studio from any directory.</p>
-    </li>
-  </ol>
-</li>
-</ol>
-
-<p>That's it! You're ready to start developing apps with Android Studio.</p>
-
-<div class="note">
-<p><strong>Note:</strong> On Windows and Mac, the individual tools and
-other SDK packages are saved within the Android Studio application directory.
-To access the tools directly, use a terminal to navigate into the application and locate
-the {@code sdk/} directory. For example:</p>
-<p>Windows: <code>\Users\&lt;user&gt;\AppData\Local\Android\android-studio\sdk\</code></p>
-<p>Mac: <code>/Applications/Android\ Studio.app/sdk/</code></p>
-</div>
-
-<p>For a list of some known issues, see <a
-href="http://tools.android.com/knownissues">tools.android.com/knownissues</a>.</p>
-
-
-<h2 id="Start">Starting a Project</h2>
-
-<p>When you launch Android Studio for the first time, you'll see a Welcome
-screen that offers several ways to get started:</p>
-
-<ul>
-  <li>To start building a new app, click <strong>New Project</strong>.
-    <p>This starts the New Project wizard, which helps you set up a project using an app template.
-  </li>
-  <li>To import an existing Android app project, click <strong>Import Project</strong>.
-    <p class="note"><strong>Note:</strong> If you previously developed your Android project
-    with Eclipse, you should first use the new export feature in the ADT plugin to prepare
-    your project with the new Gradle build system. For more information, read
-    <a href="{@docRoot}sdk/installing/migrate.html">Migrating from Eclipse</a>.</p>
-  </li>
-</ul>
-
-<p>For additional help using Android Studio, read <a
-href="{@docRoot}sdk/installing/studio-tips.html">Tips and Tricks</a>.</p>
-
-
 <p>As you continue developing apps, you may need to install additional versions
 of Android for the emulator and other packages such as the <a
 href="{@docRoot}tools/support-library/index.html">Android Support Library</a>.
@@ -642,9 +548,13 @@
 
   function onDownloadForRealz(link) {
     if ($("input#agree").is(':checked')) {
-      $("#tos").hide();
-      $("#main").show();
-      location.hash = "Updating";
+      $("h1").text('Now downloading Android Studio...');
+      $("#tos").slideUp();
+      $("#jd-content .jd-descr").fadeOut('slow', function() {
+        setTimeout(function() {
+          window.location = "/sdk/installing/index.html?pkg=studio";
+        }, 1000);
+      });
       _gaq.push(['_trackEvent', 'SDK', 'Android Studio', $("#downloadForRealz").html()]);
       return true;
     } else {
diff --git a/docs/html/tools/device.jd b/docs/html/tools/device.jd
index e9caa44..e748b12 100644
--- a/docs/html/tools/device.jd
+++ b/docs/html/tools/device.jd
@@ -280,6 +280,10 @@
     <td><code>0fce</code></td>
   </tr>
   <tr>
+    <td>Sony Mobile Communications</td>
+    <td><code>0fce</code></td>
+  </tr>
+  <tr>
     <td>Teleepoch</td>
     <td><code>2340</code></td>
   </tr>
diff --git a/docs/html/tools/help/sdk-manager.jd b/docs/html/tools/help/sdk-manager.jd
index 57271bb..b084237 100644
--- a/docs/html/tools/help/sdk-manager.jd
+++ b/docs/html/tools/help/sdk-manager.jd
@@ -3,7 +3,9 @@
 
 
 <p>The Android SDK separates tools, platforms, and other components into packages you can
-  download using the SDK Manager.</p>
+  download using the SDK Manager. For example, when the SDK Tools are updated or a new version of
+the Android platform is released, you can use the SDK Manager to quickly download them to
+your environment.</p>
 
 <p>You can launch the SDK Manager in one of the following ways:</p>
 <ul>
@@ -25,6 +27,14 @@
 SDK packages that are available, already installed, or for which an update is available.</p>
 
 
+<p>There are several different packages available for the Android SDK. The table below describes
+most of the available packages and where they're located in your SDK directory
+once you download them.</p>
+
+
+
+
+
 <h2 id="Recommended">Recommended Packages</h2>
 
 <p>Here's an outline of the packages required and those we recommend you use:
@@ -69,3 +79,77 @@
 <p class="note"><strong>Tip:</strong> For easy access to the SDK tools from a command line, add the
 location of the SDK's <code>tools/</code> and
 <code>platform-tools</code> to your <code>PATH</code> environment variable.</p>
+
+
+<p>The above list is not comprehensive and you can <a
+href="#AddingSites">add new sites</a> to download additional packages from third-parties.</p>
+
+<p>In some cases, an SDK package may require a specific minimum revision of
+another package or SDK tool.
+The development tools will notify you with warnings if there is dependency that you need to
+address. The Android SDK Manager also enforces dependencies by requiring that you download any
+packages that are needed by those you have selected.</p>
+
+
+
+
+
+<h2 id="AddingSites">Adding New Sites</h2>
+
+<p>By default, <strong>Available Packages</strong> displays packages available from the
+<em>Android Repository</em> and <em>Third party Add-ons</em>. You can add other sites that host
+their own Android SDK add-ons, then download the SDK add-ons
+from those sites.</p>
+
+<p>For example, a mobile carrier or device manufacturer might offer additional
+API libraries that are supported by their own Android-powered devices. In order
+to develop using their libraries, you must install their Android SDK add-on, if it's not already
+available under <em>Third party Add-ons</em>. </p>
+
+<p>If a carrier or device manufacturer has hosted an SDK add-on repository file
+on their web site, follow these steps to add their site to the Android SDK
+Manager:</p>
+
+<ol>
+  <li>Select <strong>Available Packages</strong> in the left panel.</li>
+  <li>Click <strong>Add Add-on Site</strong> and enter the URL of the
+<code>repository.xml</code> file. Click <strong>OK</strong>.</li>
+</ol>
+<p>Any SDK packages available from the site will now be listed under a new item named
+<strong>User Add-ons</strong>.</p>
+
+
+
+
+<h2 id="troubleshooting">Troubleshooting</h2>
+
+<p><strong>Problems connecting to the SDK repository</strong></p>
+
+<p>If you are using the Android SDK Manager to download packages and are encountering
+connection problems, try connecting over http, rather than https. To switch the
+protocol used by the Android SDK Manager, follow these steps: </p>
+
+<ol>
+  <li>With the Android SDK Manager window open, select "Settings" in the
+  left pane. </li>
+  <li>On the right, in the "Misc" section, check the checkbox labeled "Force
+  https://... sources to be fetched using http://..." </li>
+  <li>Click <strong>Save &amp; Apply</strong>.</li>
+</ol>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/html/tools/sdk/tools-notes.jd b/docs/html/tools/sdk/tools-notes.jd
index 9b06a9d..f490053 100644
--- a/docs/html/tools/sdk/tools-notes.jd
+++ b/docs/html/tools/sdk/tools-notes.jd
@@ -28,6 +28,39 @@
 <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.6.4</a> <em>(June 2014)</em>
+  </p>
+
+  <div class="toggle-content-toggleme">
+
+    <dl>
+    <dt>Dependencies:</dt>
+
+    <dd>
+      <ul>
+        <li>Android SDK Platform-tools revision 18 or later.</li>
+        <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is
+          designed for use with ADT 22.6.3 and later. If you haven't already, update your
+        <a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 22.6.3.</li>
+        <li>If you are developing outside Eclipse, 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 an issue with the x86 emulator that caused Google Maps to crash.
+            (<a href="http://b.android.com/69385">Issue 69385</a>)</li>
+        <li>Fixed minor OpenGL issues.</li>
+      </ul>
+    </dd>
+  </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.6.3</a> <em>(April 2014)</em>
   </p>
 
diff --git a/docs/html/tools/tools_toc.cs b/docs/html/tools/tools_toc.cs
index b29b87c..450a5c4 100644
--- a/docs/html/tools/tools_toc.cs
+++ b/docs/html/tools/tools_toc.cs
@@ -10,44 +10,32 @@
     <div class="nav-section-header"><a href="<?cs var:toroot
 ?>sdk/index.html"><span class="en">Download</span></a></div>
     <ul>
-      <li><a href="<?cs var:toroot ?>sdk/installing/bundle.html">
-          <span class="en">Setting Up the ADT Bundle</span></a></li>
+      <li><a href="<?cs var:toroot ?>sdk/installing/index.html">
+          <span class="en">Installing the SDK</span></a></li>
 
-      <li class="nav-section">
-        <div class="nav-section-header">
-          <a href="<?cs var:toroot ?>sdk/installing/index.html"><span class="en">Setting Up
-    an Existing IDE</span></a></div>
-        <ul>
-          <li><a href="<?cs var:toroot ?>sdk/installing/installing-adt.html">
-              <span class="en">Installing the Eclipse Plugin</span></a></li>
-        <li><a href="<?cs var:toroot ?>sdk/installing/adding-packages.html">
-            <span class="en">Adding Platforms and Packages</span></a></li>
-        </ul>
-      </li>
-
-      <li class="nav-section">
-        <div class="nav-section-header">
-          <a href="<?cs var:toroot ?>sdk/installing/studio.html">Android Studio</a>
-        </div>
-        <ul>
-          <li><a href="<?cs var:toroot ?>sdk/installing/migrate.html">
-              Migrating from Eclipse</a></li>
-          <li><a href="<?cs var:toroot ?>sdk/installing/studio-tips.html">
-              Tips and Tricks</a></li>
-          <li><a href="<?cs var:toroot ?>sdk/installing/studio-layout.html">
-              Using the Layout Editor</a></li>
-          <li><a href="<?cs var:toroot ?>sdk/installing/studio-build.html">
-              Building Your Project with Gradle</a></li>
-          <li><a href="<?cs var:toroot ?>sdk/installing/studio-debug.html">
-              Debugging with Android Studio</a></li>
-          </ul>
-      </li>
-      <li><a href="<?cs var:toroot ?>sdk/exploring.html">
-          <span class="en">Exploring the SDK</span></a></li>
-      <li><a href="<?cs var:toroot ?>tools/sdk/ndk/index.html">Download the NDK</a>
-      </li>
+      <li><a href="<?cs var:toroot ?>sdk/installing/adding-packages.html">
+          <span class="en">Adding SDK Packages</span></a></li>
     </ul>
   </li>
+  
+
+  <li class="nav-section">
+    <div class="nav-section-header">
+      <a href="<?cs var:toroot ?>sdk/installing/studio.html">Android Studio</a>
+    </div>
+    <ul>
+      <li><a href="<?cs var:toroot ?>sdk/installing/migrate.html">
+          Migrating from Eclipse</a></li>
+      <li><a href="<?cs var:toroot ?>sdk/installing/studio-tips.html">
+          Tips and Tricks</a></li>
+      <li><a href="<?cs var:toroot ?>sdk/installing/studio-layout.html">
+          Using the Layout Editor</a></li>
+      <li><a href="<?cs var:toroot ?>sdk/installing/studio-build.html">
+          Building Your Project with Gradle</a></li>
+      <li><a href="<?cs var:toroot ?>sdk/installing/studio-debug.html">
+          Debugging with Android Studio</a></li>
+      </ul>
+  </li>
 
   <li class="nav-section">
     <div class="nav-section-header">
@@ -174,7 +162,13 @@
 class="en">Tools Help</span></a></div>
     <ul>
       <li><a href="<?cs var:toroot ?>tools/help/adb.html">adb</a></li>
-      <li><a href="<?cs var:toroot ?>tools/help/adt.html">ADT</a></li>
+      <li class="nav-section">
+        <div class="nav-section-header"><a href="<?cs var:toroot ?>tools/help/adt.html">ADT</a></div>
+        <ul>
+          <li><a href="<?cs var:toroot ?>sdk/installing/installing-adt.html">
+              <span class="en">Installing the Eclipse Plugin</span></a></li>
+        </ul>
+      </li>
       <li><a href="<?cs var:toroot ?>tools/help/android.html">android</a></li>
       <li><a href="<?cs var:toroot ?>tools/help/avd-manager.html">AVD Manager</a></li>
       <li><a href="<?cs var:toroot ?>tools/help/bmgr.html">bmgr</a>
@@ -244,6 +238,11 @@
     </ul>
   </li>
 
+  <li class="nav-section">
+    <div class="nav-section-header empty">
+      <a href="<?cs var:toroot ?>tools/sdk/ndk/index.html">NDK</a>
+    </div>
+  </li>
 
   <li class="nav-section">
     <div class="nav-section-header">
diff --git a/docs/html/training/articles/keystore.jd b/docs/html/training/articles/keystore.jd
new file mode 100644
index 0000000..bbbda67
--- /dev/null
+++ b/docs/html/training/articles/keystore.jd
@@ -0,0 +1,107 @@
+page.title=Android Keystore System
+@jd:body
+
+<div id="qv-wrapper">
+  <div id="qv">
+    <h2>In this document</h2>
+    <ol>
+      <li><a href="#WhichShouldIUse">Choosing Between a Keychain or the Android Keystore Provider</a></li>
+      <li><a href="#UsingAndroidKeyStore">Using Android Keystore Provider
+      </a></li>
+      <ol>
+        <li><a href="#GeneratingANewPrivateKey">Generating a New Private Key</a></li>
+        <li><a href="#WorkingWithKeyStoreEntries">Working with Keystore Entries</a></li>
+        <li><a href="#ListingEntries">Listing Entries</a></li>
+        <li><a href="#SigningAndVerifyingData">Signing and Verifying Data</a></li>
+      </ol>
+    </ol>
+
+    <h2>Blog articles</h2>
+    <ol>
+      <li><a
+        href="http://android-developers.blogspot.com/2012/03/unifying-key-store-access-in-ics.html">
+          <h4>Unifying Key Store Access in ICS</h4>
+      </a></li>
+    </ol>
+  </div>
+</div>
+
+<p>The Android Keystore system lets you store private keys
+  in a container to make it more difficult to extract from the
+  device. Once keys are in the keystore, they can be used for
+  cryptographic operations with the private key material remaining
+  non-exportable.</p>
+
+<p>The Keystore system is used by the {@link
+  android.security.KeyChain} API as well as the Android
+  Keystore provider feature that was introduced in Android 4.3
+  (API level 18). This document goes over when and how to use the
+  Android Keystore provider.</p>
+
+<h2 id="WhichShouldIUse">Choosing Between a Keychain or the
+Android Keystore Provider</h2>
+
+<p>Use the {@link android.security.KeyChain} API when you want
+  system-wide credentials. When an app requests the use of any credential
+  through the {@link android.security.KeyChain} API, users get to
+  choose, through a system-provided UI, which of the installed credentials
+  an app can access. This allows several apps to use the
+  same set of credentials with user consent.</p>
+
+<p>Use the Android Keystore provider to let an individual app store its own
+  credentials that only the app itself can access.
+  This provides a way for apps to manage credentials that are usable
+  only by itself while providing the same security benefits that the
+  {@link android.security.KeyChain} API provides for system-wide
+  credentials. This method requires no user interaction to select the credentials.</p>
+
+<h2 id="UsingAndroidKeyStore">Using Android Keystore Provider</h2>
+
+<p>
+To use this feature, you use the standard {@link java.security.KeyStore}
+and {@link java.security.KeyPairGenerator} classes along with the
+{@code AndroidKeyStore} provider introduced in Android 4.3 (API level 18).</p>
+
+<p>{@code AndroidKeyStore} is registered as a {@link
+  java.security.KeyStore} type for use with the {@link
+  java.security.KeyStore#getInstance(String) KeyStore.getInstance(type)}
+  method and as a provider for use with the {@link
+  java.security.KeyPairGenerator#getInstance(String, String)
+  KeyPairGenerator.getInstance(algorithm, provider)} method.</p>
+
+<h3 id="GeneratingANewPrivateKey">Generating a New Private Key</h3>
+
+<p>Generating a new {@link java.security.PrivateKey} requires that
+  you also specify the initial X.509 attributes that the self-signed
+  certificate will have. You can replace the certificate at a later
+  time with a certificate signed by a Certificate Authority.</p>
+
+<p>To generate the key, use a {@link java.security.KeyPairGenerator}
+  with {@link android.security.KeyPairGeneratorSpec}:</p>
+
+{@sample development/samples/ApiDemos/src/com/example/android/apis/security/KeyStoreUsage.java generate}
+
+<h3 id="WorkingWithKeyStoreEntries">Working with Keystore Entries</h3>
+
+<p>Using the {@code AndroidKeyStore} provider takes place through
+  all the standard {@link java.security.KeyStore} APIs.</p>
+
+<h4 id="ListingEntries">Listing Entries</h4>
+
+<p>List entries in the keystore by calling the {@link
+  java.security.KeyStore#aliases()} method:</p>
+
+{@sample development/samples/ApiDemos/src/com/example/android/apis/security/KeyStoreUsage.java list}
+
+<h4 id="SigningAndVerifyingData">Signing and Verifying Data</h4>
+
+<p>Sign data by fetching the {@link
+  java.security.KeyStore.Entry} from the keystore and using the
+  {@link java.security.Signature} APIs, such as {@link
+  java.security.Signature#sign()}:</p>
+
+{@sample development/samples/ApiDemos/src/com/example/android/apis/security/KeyStoreUsage.java sign}
+
+<p>Similarly, verify data with the {@link java.security.Signature#verify(byte[])} method:</p>
+
+{@sample development/samples/ApiDemos/src/com/example/android/apis/security/KeyStoreUsage.java verify}
diff --git a/docs/html/training/basics/network-ops/connecting.jd b/docs/html/training/basics/network-ops/connecting.jd
index 50a9e1b..1452ded 100644
--- a/docs/html/training/basics/network-ops/connecting.jd
+++ b/docs/html/training/basics/network-ops/connecting.jd
@@ -25,6 +25,7 @@
 
 <h2>You should also read</h2>
 <ul>
+  <li><a href="{@docRoot}training/volley/index.html">Transmitting Network Data Using Volley</a></li>
   <li><a href="{@docRoot}training/monitoring-device-state/index.html">Optimizing Battery Life</a></li>
   <li><a href="{@docRoot}training/efficient-downloads/index.html">Transferring Data Without Draining the Battery</a></li>
   <li><a href="{@docRoot}guide/webapps/index.html">Web Apps Overview</a></li>
diff --git a/docs/html/training/basics/network-ops/index.jd b/docs/html/training/basics/network-ops/index.jd
index 89ab539..1f6493f 100644
--- a/docs/html/training/basics/network-ops/index.jd
+++ b/docs/html/training/basics/network-ops/index.jd
@@ -24,6 +24,7 @@
   <li><a href="{@docRoot}training/monitoring-device-state/index.html">Optimizing Battery Life</a></li>
   <li><a href="{@docRoot}training/efficient-downloads/index.html">Transferring Data Without Draining the Battery</a></li>
   <li><a href="{@docRoot}guide/webapps/index.html">Web Apps Overview</a></li>
+  <li><a href="{@docRoot}training/volley/index.html">Transmitting Network Data Using Volley</a></li>
 </ul>
 
 
@@ -51,6 +52,14 @@
 fundamental building blocks for creating Android applications that download
 content and parse data efficiently, while minimizing network traffic.</p>
 
+<p class="note"><strong>Note:</strong> See the class <a href="{@docRoot}
+training/volley/index.html">Transmitting Network Data Using Volley</a>
+for information on Volley, an HTTP library that makes networking for Android apps
+easier and faster. Volley is available through the open
+<a href="https://android.googlesource.com/platform/frameworks/volley">AOSP</a>
+repository. Volley may be able to help you streamline and improve the performance
+of your app's network operations.</p>
+
 
 
 <h2>Lessons</h2>
diff --git a/docs/html/training/contacts-provider/retrieve-names.jd b/docs/html/training/contacts-provider/retrieve-names.jd
index b034a6a..7106889a 100644
--- a/docs/html/training/contacts-provider/retrieve-names.jd
+++ b/docs/html/training/contacts-provider/retrieve-names.jd
@@ -102,9 +102,9 @@
 <p>
     To display the search results in a {@link android.widget.ListView}, you need a main layout file
     that defines the entire UI including the {@link android.widget.ListView}, and an item layout
-    file that defines one line of the {@link android.widget.ListView}. For example, you can define
-    the main layout file <code>res/layout/contacts_list_view.xml</code> that contains the
-    following XML:
+    file that defines one line of the {@link android.widget.ListView}. For example, you could create
+    the main layout file <code>res/layout/contacts_list_view.xml</code> with
+    the following XML:
 </p>
 <pre>
 &lt;?xml version="1.0" encoding="utf-8"?&gt;
@@ -250,7 +250,8 @@
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         // Inflate the fragment layout
-        return inflater.inflate(R.layout.contacts_list_layout, container, false);
+        return inflater.inflate(R.layout.contact_list_fragment,
+            container, false);
     }
 </pre>
 <h3 id="DefineAdapter">Set up the CursorAdapter for the ListView</h3>
@@ -268,7 +269,8 @@
         super.onActivityCreated(savedInstanceState);
         ...
         // Gets the ListView from the View list of the parent activity
-        mContactsList = (ListView) getActivity().findViewById(R.layout.contact_list_view);
+        mContactsList =
+            (ListView) getActivity().findViewById(R.layout.contact_list_view);
         // Gets a CursorAdapter
         mCursorAdapter = new SimpleCursorAdapter(
                 getActivity(),
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index 0616b62..c5dc3c5 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -609,6 +609,35 @@
             </li>
         </ul>
       </li>
+      <li class="nav-section">
+        <div class="nav-section-header">
+          <a href="<?cs var:toroot ?>training/volley/index.html"
+             description="How to perform fast, scalable UI operations over the network using Volley"
+             >Transmitting Network Data Using Volley</a>
+        </div>
+        <ul>
+            <li>
+                <a href="<?cs var:toroot ?>training/volley/simple.html">
+                Sending a Simple Request
+                </a>
+            </li>
+            <li>
+                <a href="<?cs var:toroot ?>training/volley/requestqueue.html">
+                Setting Up a RequestQueue
+                </a>
+            </li>
+            <li>
+                <a href="<?cs var:toroot ?>training/volley/request.html">
+                Making a Standard Request
+                </a>
+            </li>
+            <li>
+                <a href="<?cs var:toroot ?>training/volley/request-custom.html">
+                Implementing a Custom Request
+                </a>
+            </li>
+        </ul>
+      </li>
 
     </ul>
   </li>
diff --git a/docs/html/training/volley/index.jd b/docs/html/training/volley/index.jd
new file mode 100644
index 0000000..ba5b09f
--- /dev/null
+++ b/docs/html/training/volley/index.jd
@@ -0,0 +1,133 @@
+page.title=Transmitting Network Data Using Volley
+page.tags=""
+
+trainingnavtop=true
+startpage=true
+
+
+@jd:body
+
+
+
+<div id="tb-wrapper">
+<div id="tb">
+
+
+<!-- Required platform, tools, add-ons, devices, knowledge, etc. -->
+<h2>Dependencies and prerequisites</h2>
+
+<ul>
+  <li>Android 1.6 (API Level 4) or higher</li>
+</ul>
+
+<h2>You should also see</h2>
+<ul>
+  <li>For a production quality app that uses Volley, see the 2013 Google I/O
+  <a href="https://github.com/google/iosched">schedule app</a>. In particular, see:
+    <ul>
+      <li><a
+      href="https://github.com/google/iosched/blob/master/android/src/main/java/com/google/android/apps/iosched/util/ImageLoader.java">
+      ImageLoader</a></li>
+      <li><a
+      href="https://github.com/google/iosched/blob/master/android/src/main/java/com/google/android/apps/iosched/util/BitmapCache.java">
+      BitmapCache</a></li>
+    </ul>
+  </li>
+</ul>
+
+</div>
+</div>
+
+<a class="notice-developers-video wide" href="https://developers.google.com/events/io/sessions/325304728">
+<div>
+    <h3>Video</h3>
+    <p>Volley: Easy, Fast Networking for Android</p>
+</div>
+</a>
+
+
+<p>Volley is an HTTP library that makes networking for Android apps easier and most importantly,
+faster. Volley is available through the open
+<a href="https://android.googlesource.com/platform/frameworks/volley">AOSP</a> repository.</p>
+
+<p>Volley offers the following benefits:</p>
+
+<ul>
+
+<li>Automatic scheduling of network requests.</li>
+<li>Multiple concurrent network connections.</li>
+<li>Transparent disk and memory response caching with standard HTTP
+<a href=http://en.wikipedia.org/wiki/Cache_coherence">cache coherence</a>.</li>
+<li>Support for request prioritization.</li>
+<li>Cancellation request API. You can cancel a single request, or you can set blocks or
+scopes of requests to cancel.</li>
+<li>Ease of customization, for example, for retry and backoff.</li>
+<li>Strong ordering that makes it easy to correctly populate your UI with data fetched
+asynchronously from the network.</li>
+<li>Debugging and tracing tools.</li>
+
+</ul>
+
+<p>Volley excels at RPC-type operations used to populate a UI, such as fetching a page of
+search results as structured data. It integrates easily with any protocol and comes out of
+the box with support for raw strings, images, and JSON. By providing built-in support for
+the features you need, Volley frees you from writing boilerplate code and allows you to
+concentrate on the logic that is specific to your app.</p>
+<p>Volley is not suitable for large download or streaming operations, since Volley holds
+all responses in memory during parsing. For large download operations, consider using an
+alternative like {@link android.app.DownloadManager}.</p>
+
+<p>The core Volley library is developed in the open
+<a href="https://android.googlesource.com/platform/frameworks/volley">AOSP</a>
+repository at {@code frameworks/volley} and contains the main request dispatch pipeline
+as well as a set of commonly applicable utilities, available in the Volley "toolbox." The
+easiest way to add Volley to your project is to clone the Volley repository and set it as
+a library project:</p>
+
+<ol>
+<li>Git clone the repository by typing the following at the command line:
+
+<pre>
+git clone https://android.googlesource.com/platform/frameworks/volley
+</pre>
+</li>
+
+<li>Import the downloaded source into your app project as an Android library project
+(as described in <a href="{@docRoot}tools/projects/projects-eclipse.html">
+Managing Projects from Eclipse with ADT</a>, if you're using Eclipse) or make a
+<a href="{@docRoot}guide/faq/commontasks.html#addexternallibrary"><code>.jar</code> file</a>.</li>
+</ol>
+
+<h2>Lessons</h2>
+
+<dl>
+ <dt>
+        <strong><a href="simple.html">Sending a Simple Request</a></strong>
+    </dt>
+    <dd>
+        Learn how to send a simple request using the default behaviors of Volley, and how
+        to cancel a request.
+
+    </dd>
+    <dt>
+        <strong><a href="requestqueue.html">Setting Up a RequestQueue</a></strong>
+    </dt>
+    <dd>
+        Learn how to set up a {@code RequestQueue}, and how to implement a singleton
+        pattern to create a {@code RequestQueue} that lasts the lifetime of your app.
+    </dd>
+    <dt>
+        <strong><a href="request.html">Making a Standard Request</a></strong>
+    </dt>
+    <dd>
+        Learn how to send a request using one of Volley's out-of-the-box request types
+        (raw strings, images, and JSON).
+    </dd>
+    <dt>
+        <strong><a href="request-custom.html">Implementing a Custom Request</a></strong>
+    </dt>
+    <dd>
+        Learn how to implement a custom request.
+    </dd>
+
+</dl>
diff --git a/docs/html/training/volley/request-custom.jd b/docs/html/training/volley/request-custom.jd
new file mode 100644
index 0000000..7b669b9
--- /dev/null
+++ b/docs/html/training/volley/request-custom.jd
@@ -0,0 +1,163 @@
+page.title=Implementing a Custom Request
+
+trainingnavtop=true
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<!-- table of contents -->
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#custom-request">Write a Custom Request</a></li>
+</ol>
+
+</div>
+</div>
+
+<a class="notice-developers-video wide" href="https://developers.google.com/events/io/sessions/325304728">
+<div>
+    <h3>Video</h3>
+    <p>Volley: Easy, Fast Networking for Android</p>
+</div>
+</a>
+
+<p>This lesson describes how to implement your own custom request types, for types that
+don't have out-of-the-box Volley support.</p>
+
+<h2 id="custom-request">Write a Custom Request</h2>
+
+Most requests have ready-to-use implementations in the toolbox; if your response is a string,
+image, or JSON, you probably won't need to implement a custom {@code Request}.</p>
+
+<p>For cases where you do need to implement a custom request, this is all you need
+to do:</p>
+
+<ul>
+
+<li>Extend the {@code Request&lt;T&gt;} class, where
+{@code &lt;T&gt;} represents the type of parsed response
+the request expects. So if your parsed response is a string, for example,
+create your custom request by extending {@code Request&lt;String&gt;}. See the Volley
+toolbox classes {@code StringRequest} and {@code ImageRequest} for examples of
+extending {@code Request&lt;T&gt;}.</li>
+
+<li>Implement the abstract methods {@code parseNetworkResponse()}
+and {@code deliverResponse()}, described in more detail below.</li>
+
+</ul>
+
+<h3>parseNetworkResponse</h3>
+
+<p>A {@code Response} encapsulates a parsed response for delivery, for a given type
+(such as string, image, or JSON). Here is a sample implementation of
+{@code parseNetworkResponse()}:</p>
+
+<pre>
+&#64;Override
+protected Response&lt;T&gt; parseNetworkResponse(
+        NetworkResponse response) {
+    try {
+        String json = new String(response.data,
+        HttpHeaderParser.parseCharset(response.headers));
+    return Response.success(gson.fromJson(json, clazz),
+    HttpHeaderParser.parseCacheHeaders(response));
+    }
+    // handle errors
+...
+}
+</pre>
+
+<p>Note the following:</p>
+
+<ul>
+<li>{@code parseNetworkResponse()} takes as its parameter a {@code NetworkResponse}, which
+contains the response payload as a byte[], HTTP status code, and response headers.</li>
+<li>Your implementation must return a {@code Response&lt;T&gt;}, which contains your typed
+response object and cache metadata or an error, such as in the case of a parse failure.</li>
+</ul>
+
+<p>If your protocol has non-standard cache semantics, you can build a {@code Cache.Entry}
+yourself, but most requests are fine with something like this:
+</p>
+<pre>return Response.success(myDecodedObject,
+        HttpHeaderParser.parseCacheHeaders(response));</pre>
+<p>
+Volley calls {@code parseNetworkResponse()} from a worker thread. This ensures that
+expensive parsing operations, such as decoding a JPEG into a Bitmap, don't block the UI
+thread.</p>
+
+<h3>deliverResponse</h3>
+
+<p>Volley calls you back on the main thread with the object you returned in
+{@code parseNetworkResponse()}. Most requests invoke a callback interface here,
+for example:
+</p>
+
+<pre>
+protected void deliverResponse(T response) {
+        listener.onResponse(response);
+</pre>
+
+<h3>Example: GsonRequest</h3>
+
+<p><a href="http://code.google.com/p/google-gson/">Gson</a> is a library for converting
+Java objects to and from JSON using reflection. You can define Java objects that have the
+same names as their corresponding JSON keys, pass Gson the class object, and Gson will fill
+in the fields for you. Here's a complete implementation of a Volley request that uses
+Gson for parsing:</p>
+
+<pre>
+public class GsonRequest&lt;T&gt; extends Request&lt;T&gt; {
+    private final Gson gson = new Gson();
+    private final Class&lt;T&gt; clazz;
+    private final Map&lt;String, String&gt; headers;
+    private final Listener&lt;T&gt; listener;
+
+    /**
+     * Make a GET request and return a parsed object from JSON.
+     *
+     * &#64;param url URL of the request to make
+     * &#64;param clazz Relevant class object, for Gson's reflection
+     * &#64;param headers Map of request headers
+     */
+    public GsonRequest(String url, Class&lt;T&gt; clazz, Map&lt;String, String&gt; headers,
+            Listener&lt;T&gt; listener, ErrorListener errorListener) {
+        super(Method.GET, url, errorListener);
+        this.clazz = clazz;
+        this.headers = headers;
+        this.listener = listener;
+    }
+
+    &#64;Override
+    public Map&lt;String, String&gt; getHeaders() throws AuthFailureError {
+        return headers != null ? headers : super.getHeaders();
+    }
+
+    &#64;Override
+    protected void deliverResponse(T response) {
+        listener.onResponse(response);
+    }
+
+    &#64;Override
+    protected Response&lt;T&gt; parseNetworkResponse(NetworkResponse response) {
+        try {
+            String json = new String(
+                    response.data,
+                    HttpHeaderParser.parseCharset(response.headers));
+            return Response.success(
+                    gson.fromJson(json, clazz),
+                    HttpHeaderParser.parseCacheHeaders(response));
+        } catch (UnsupportedEncodingException e) {
+            return Response.error(new ParseError(e));
+        } catch (JsonSyntaxException e) {
+            return Response.error(new ParseError(e));
+        }
+    }
+}
+</pre>
+
+<p>Volley provides ready-to-use {@code JsonArrayRequest} and {@code JsonArrayObject} classes
+if you prefer to take that approach. See <a href="request.html">
+Using Standard Request Types</a> for more information.</p>
diff --git a/docs/html/training/volley/request.jd b/docs/html/training/volley/request.jd
new file mode 100644
index 0000000..d8ccab2
--- /dev/null
+++ b/docs/html/training/volley/request.jd
@@ -0,0 +1,281 @@
+page.title=Making a Standard Request
+
+trainingnavtop=true
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<!-- table of contents -->
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#request-image">Request an Image</a></li>
+  <li><a href="#request-json">Request JSON</a></li>
+</ol>
+
+</div>
+</div>
+
+<a class="notice-developers-video wide" href="https://developers.google.com/events/io/sessions/325304728">
+<div>
+    <h3>Video</h3>
+    <p>Volley: Easy, Fast Networking for Android</p>
+</div>
+</a>
+
+<p>
+This lesson describes how to use the common request types that Volley supports:</p>
+
+<ul>
+  <li>{@code StringRequest}. Specify a URL and receive a raw string in response. See
+  <a href="requestqueue.html">Setting Up a Request Queue</a> for an example.</li>
+  <li>{@code ImageRequest}. Specify a URL and receive an image in response.</li>
+  <li>{@code JsonObjectRequest} and {@code JsonArrayRequest} (both subclasses of
+  {@code JsonRequest}). Specify a URL and get a JSON object or array (respectively) in
+  response.</li>
+</ul>
+
+<p>If your expected response is one of these types, you probably won't have to implement a
+custom request. This lesson describes how to use these standard request types. For
+information on how to implement your own custom request, see <a href="requests-custom.html">
+Implementing a Custom Request</a>.</p>
+
+
+<h2 id="request-image">Request an Image</h2>
+
+<p>Volley offers the following classes for requesting images. These classes layer on top
+of each other to offer different levels of support for processing images:</p>
+
+<ul>
+  <li>{@code ImageRequest}&mdash;a canned request for getting an image at a given URL and
+  calling back with a decoded bitmap. It also provides convenience features like specifying
+  a size to resize to. Its main benefit is that Volley's thread scheduling ensures that
+  expensive image operations (decoding, resizing) automatically happen on a worker thread.</li>
+
+  <li>{@code ImageLoader}&mdash;a helper class that handles loading and caching images from
+  remote URLs. {@code ImageLoader} is a an orchestrator for large numbers of {@code ImageRequest}s,
+  for example when putting multiple thumbnails in a {@link android.widget.ListView}.
+  {@code ImageLoader} provides an in-memory cache to sit in front of the normal Volley
+  cache, which is important to prevent flickering. This makes it possible to achieve a
+  cache hit without blocking or deferring off the main thread, which is impossible when
+  using disk I/O. {@code ImageLoader} also does response coalescing, without which almost
+  every response handler would set a bitmap on a view and cause a layout pass per image.
+  Coalescing makes it possible to deliver multiple responses simultaneously, which improves
+  performance.</li>
+  <li>{@code NetworkImageView}&mdash;builds on {@code ImageLoader} and effectively replaces
+  {@link android.widget.ImageView} for situations where your image is being fetched over
+  the network via URL. {@code NetworkImageView} also manages canceling pending requests if
+  the view is detached from the hierarchy.</li>
+</ul>
+
+<h3>Use ImageRequest</h3>
+
+<p>Here is an example of using {@code ImageRequest}. It retrieves the image specified by
+the URL and displays it in the app. Note that this snippet interacts with the
+{@code RequestQueue} through a singleton class (see <a href="{@docRoot}
+training/volley/requestqueue.html#singleton">Setting Up a RequestQueue</a> for more discussion of
+this topic):</p>
+
+<pre>
+ImageView mImageView;
+String url = "http://i.imgur.com/7spzG.png";
+mImageView = (ImageView) findViewById(R.id.myImage);
+...
+
+// Retrieves an image specified by the URL, displays it in the UI.
+ImageRequest request = new ImageRequest(url,
+    new Response.Listener<Bitmap>() {
+        &#64;Override
+        public void onResponse(Bitmap bitmap) {
+            mImageView.setImageBitmap(bitmap);
+        }
+    }, 0, 0, null,
+    new Response.ErrorListener() {
+        public void onErrorResponse(VolleyError error) {
+            mImageView.setImageResource(R.drawable.image_load_error);
+        }
+    });
+// Access the RequestQueue through your singleton class.
+MySingleton.getInstance(this).addToRequestQueue(request);</pre>
+
+
+<h3>Use ImageLoader and NetworkImageView</h3>
+
+<p>You can use {@code ImageLoader} and {@code NetworkImageView} in concert to efficiently
+manage the display of multiple images, such as in a {@link android.widget.ListView}. In your
+layout XML file, you use {@code NetworkImageView} in much the same way you would use
+{@link android.widget.ImageView}, for example:</p>
+
+<pre>&lt;com.android.volley.toolbox.NetworkImageView
+        android:id=&quot;&#64;+id/networkImageView&quot;
+        android:layout_width=&quot;150dp&quot;
+        android:layout_height=&quot;170dp&quot;
+        android:layout_centerHorizontal=&quot;true&quot; /&gt;</pre>
+
+<p>You can use {@code ImageLoader} by itself to display an image, for example:</p>
+
+<pre>
+ImageLoader mImageLoader;
+ImageView mImageView;
+// The URL for the image that is being loaded.
+private static final String IMAGE_URL =
+    "http://developer.android.com/images/training/system-ui.png";
+...
+mImageView = (ImageView) findViewById(R.id.regularImageView);
+
+// Get the ImageLoader through your singleton class.
+mImageLoader = MySingleton.getInstance(this).getImageLoader();
+mImageLoader.get(IMAGE_URL, ImageLoader.getImageListener(mImageView,
+         R.drawable.def_image, R.drawable.err_image));
+</pre>
+
+<p>However, {@code NetworkImageView} can do this for you if all you're doing is populating
+an {@link android.widget.ImageView}. For example:</p>
+
+<pre>
+ImageLoader mImageLoader;
+NetworkImageView mNetworkImageView;
+private static final String IMAGE_URL =
+    "http://developer.android.com/images/training/system-ui.png";
+...
+
+// Get the NetworkImageView that will display the image.
+mNetworkImageView = (NetworkImageView) findViewById(R.id.networkImageView);
+
+// Get the ImageLoader through your singleton class.
+mImageLoader = MySingleton.getInstance(this).getImageLoader();
+
+// Set the URL of the image that should be loaded into this view, and
+// specify the ImageLoader that will be used to make the request.
+mNetworkImageView.setImageUrl(IMAGE_URL, mImageLoader);
+</pre>
+
+<p>The above snippets access the {@code RequestQueue} and the {@code ImageLoader}
+through a singleton class, as described in <a href="{@docRoot}training/volley/requestqueue.html#singleton">
+Setting Up a RequestQueue</a>. This approach ensures that your app creates single instances of
+these classes that last the lifetime of your app. The reason that this is important for
+{@code ImageLoader} (the helper class that handles loading and caching images) is that
+the main function of the in-memory cache is to allow for flickerless rotation. Using a
+singleton pattern allows the bitmap cache to outlive the activity. If instead you create the
+{@code ImageLoader} in an activity, the {@code ImageLoader} would be recreated along with
+the activity every time the user rotates the device. This would cause flickering.</p>
+
+<h4 id="lru-cache">Example LRU cache</h4>
+
+<p>The Volley toolbox provides a standard cache implementation via the
+{@code DiskBasedCache} class. This class caches files directly onto the hard disk in the
+specified directory. But to use {@code ImageLoader}, you should provide a custom
+in-memory LRU bitmap cache that implements the {@code ImageLoader.ImageCache} interface.
+You may want to set up your cache as a singleton; for more discussion of this topic, see
+<a href="{@docRoot}training/volley/requestqueue.html#singleton">
+Setting Up a RequestQueue</a>.</p>
+
+<p>Here is a sample implementation for an in-memory {@code LruBitmapCache} class.
+It extends the {@link android.support.v4.util.LruCache} class and implements the
+{@code ImageLoader.ImageCache} interface:</p>
+
+<pre>
+import android.graphics.Bitmap;
+import android.support.v4.util.LruCache;
+import android.util.DisplayMetrics;
+import com.android.volley.toolbox.ImageLoader.ImageCache;
+
+public class LruBitmapCache extends LruCache&lt;String, Bitmap&gt;
+        implements ImageCache {
+
+    public LruBitmapCache(int maxSize) {
+        super(maxSize);
+    }
+
+    public LruBitmapCache(Context ctx) {
+        this(getCacheSize(ctx));
+    }
+
+    &#64;Override
+    protected int sizeOf(String key, Bitmap value) {
+        return value.getRowBytes() * value.getHeight();
+    }
+
+    &#64;Override
+    public Bitmap getBitmap(String url) {
+        return get(url);
+    }
+
+    &#64;Override
+    public void putBitmap(String url, Bitmap bitmap) {
+        put(url, bitmap);
+    }
+
+    // Returns a cache size equal to approximately three screens worth of images.
+    public static int getCacheSize(Context ctx) {
+        final DisplayMetrics displayMetrics = ctx.getResources().
+                getDisplayMetrics();
+        final int screenWidth = displayMetrics.widthPixels;
+        final int screenHeight = displayMetrics.heightPixels;
+        // 4 bytes per pixel
+        final int screenBytes = screenWidth * screenHeight * 4;
+
+        return screenBytes * 3;
+    }
+}
+</pre>
+
+<p>Here is an example of how to instantiate an {@code ImageLoader} to use this
+cache:</p>
+
+<pre>
+RequestQueue mRequestQueue; // assume this exists.
+ImageLoader mImageLoader = new ImageLoader(mRequestQueue, new LruBitmapCache(
+            LruBitmapCache.getCacheSize()));
+</pre>
+
+
+<h2 id="request-json">Request JSON</h2>
+
+<p>Volley provides the following classes for JSON requests:</p>
+
+<ul>
+  <li>{@code JsonArrayRequest}&mdash;A request for retrieving a
+  {@link org.json.JSONArray}
+  response body at a given URL.</li>
+  <li>{@code JsonObjectRequest}&mdash;A request for retrieving a
+  {@link org.json.JSONObject}
+  response body at a given URL, allowing for an optional
+  {@link org.json.JSONObject}
+  to be passed in as part of the request body.</li>
+</ul>
+
+<p>Both classes are based on the common base class {@code JsonRequest}. You use them
+following the same basic pattern you use for other types of requests. For example, this
+snippet fetches a JSON feed and displays it as text in the UI:</p>
+
+<pre>
+TextView mTxtDisplay;
+ImageView mImageView;
+mTxtDisplay = (TextView) findViewById(R.id.txtDisplay);
+String url = "http://my-json-feed";
+
+JsonObjectRequest jsObjRequest = new JsonObjectRequest
+        (Request.Method.GET, url, null, new Response.Listener<JSONObject>() {
+
+    &#64;Override
+    public void onResponse(JSONObject response) {
+        mTxtDisplay.setText("Response: " + response.toString());
+    }
+}, new Response.ErrorListener() {
+
+    &#64;Override
+    public void onErrorResponse(VolleyError error) {
+        // TODO Auto-generated method stub
+
+    }
+});
+
+// Access the RequestQueue through your singleton class.
+MySingleton.getInstance(this).addToRequestQueue(jsObjRequest);
+</pre>
+
+For an example of implementing a custom JSON request based on
+<a href="http://code.google.com/p/google-gson/">Gson</a>, see the next lesson,
+<a href="request-custom.html">Implementing a Custom Request</a>.
diff --git a/docs/html/training/volley/requestqueue.jd b/docs/html/training/volley/requestqueue.jd
new file mode 100644
index 0000000..6858d91
--- /dev/null
+++ b/docs/html/training/volley/requestqueue.jd
@@ -0,0 +1,204 @@
+page.title=Setting Up a RequestQueue
+
+trainingnavtop=true
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<!-- table of contents -->
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#network">Set Up a Network and Cache</a></li>
+  <li><a href="#singleton">Use a Singleton Pattern</a></li>
+</ol>
+
+</div>
+</div>
+
+<a class="notice-developers-video wide" href="https://developers.google.com/events/io/sessions/325304728">
+<div>
+    <h3>Video</h3>
+    <p>Volley: Easy, Fast Networking for Android</p>
+</div>
+</a>
+
+
+<p>The previous lesson showed you how to use the convenience method
+<code>Volley.newRequestQueue</code> to set up a {@code RequestQueue}, taking advantage of
+Volley's default behaviors. This lesson walks you through the explicit steps of creating a
+{@code RequestQueue}, to allow you to supply your own custom behavior.</p>
+
+<p>This lesson also describes the recommended practice of creating a {@code RequestQueue}
+as a singleton, which makes the {@code RequestQueue} last the lifetime of your app.</p>
+
+<h2 id="network">Set Up a Network and Cache</h2>
+
+<p>A {@code RequestQueue} needs two things to do its job: a network to perform transport
+of the requests, and a cache to handle caching. There are standard implementations of these
+available in the Volley toolbox: {@code DiskBasedCache} provides a one-file-per-response
+cache with an in-memory index, and {@code BasicNetwork} provides a network transport based
+on your choice of {@link android.net.http.AndroidHttpClient} or {@link java.net.HttpURLConnection}.</p>
+
+<p>{@code BasicNetwork} is Volley's default network implementation. A {@code BasicNetwork}
+must be initialized with the HTTP client your app is using to connect to the network.
+Typically this is {@link android.net.http.AndroidHttpClient} or
+{@link java.net.HttpURLConnection}:</p>
+<ul>
+<li>Use {@link android.net.http.AndroidHttpClient} for apps targeting Android API levels
+lower than API Level 9 (Gingerbread). Prior to Gingerbread, {@link java.net.HttpURLConnection}
+was unreliable. For more discussion of this topic, see
+<a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">
+Android's HTTP Clients</a>. </li>
+
+<li>Use {@link java.net.HttpURLConnection} for apps targeting Android API Level 9
+(Gingerbread) and higher.</li>
+</ul>
+<p>To create an app that runs on all versions of Android, you can check the version of
+Android the device is running and choose the appropriate HTTP client, for example:</p>
+
+<pre>
+HttpStack stack;
+...
+// If the device is running a version >= Gingerbread...
+if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
+    // ...use HttpURLConnection for stack.
+} else {
+    // ...use AndroidHttpClient for stack.
+}
+Network network = new BasicNetwork(stack);
+</pre>
+
+<p>This snippet shows you the steps involved in setting up a
+{@code RequestQueue}:</p>
+
+<pre>
+RequestQueue mRequestQueue;
+
+// Instantiate the cache
+Cache cache = new DiskBasedCache(getCacheDir(), 1024 * 1024); // 1MB cap
+
+// Set up the network to use HttpURLConnection as the HTTP client.
+Network network = new BasicNetwork(new HurlStack());
+
+// Instantiate the RequestQueue with the cache and network.
+mRequestQueue = new RequestQueue(cache, network);
+
+// Start the queue
+mRequestQueue.start();
+
+String url ="http://www.myurl.com";
+
+// Formulate the request and handle the response.
+StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
+        new Response.Listener&lt;String&gt;() {
+    &#64;Override
+    public void onResponse(String response) {
+        // Do something with the response
+    }
+},
+    new Response.ErrorListener() {
+        &#64;Override
+        public void onErrorResponse(VolleyError error) {
+            // Handle error
+    }
+});
+
+// Add the request to the RequestQueue.
+mRequestQueue.add(stringRequest);
+...
+</pre>
+
+<p>If you just need to make a one-time request and don't want to leave the thread pool
+around, you can create the {@code RequestQueue} wherever you need it and call {@code stop()} on the
+{@code RequestQueue} once your response or error has come back, using the
+{@code Volley.newRequestQueue()} method described in <a href="simple.html">Sending a Simple
+Request</a>. But the more common use case is to create the {@code RequestQueue} as a
+singleton to keep it running for the lifetime of your app, as described in the next section.</p>
+
+
+<h2 id="singleton">Use a Singleton Pattern</h2>
+
+<p>If your application makes constant use of the network, it's probably most efficient to
+set up a single instance of {@code RequestQueue} that will last the lifetime of your app.
+You can achieve this in various ways. The recommended approach is to implement a singleton
+class that encapsulates {@code RequestQueue} and other Volley
+functionality. Another approach is to subclass {@link android.app.Application} and set up the
+{@code RequestQueue} in {@link android.app.Application#onCreate Application.onCreate()}.
+But this approach is <a href="{@docRoot}reference/android/app/Application.html">
+discouraged</a>; a static singleton can provide the same functionality in a more modular
+way. </p>
+
+<p>A key concept is that the {@code RequestQueue} must be instantiated with the
+{@link android.app.Application} context, not an {@link android.app.Activity} context. This
+ensures that the {@code RequestQueue} will last for the lifetime of your app, instead of
+being recreated every time the activity is recreated (for example, when the user
+rotates the device).
+
+<p>Here is an example of a singleton class that provides {@code RequestQueue} and
+{@code ImageLoader} functionality:</p>
+
+<pre>private static MySingleton mInstance;
+    private RequestQueue mRequestQueue;
+    private ImageLoader mImageLoader;
+    private static Context mCtx;
+
+    private MySingleton(Context context) {
+        mCtx = context;
+        mRequestQueue = getRequestQueue();
+
+        mImageLoader = new ImageLoader(mRequestQueue,
+                new ImageLoader.ImageCache() {
+            private final LruCache&lt;String, Bitmap&gt;
+                    cache = new LruCache&lt;String, Bitmap&gt;(20);
+
+            &#64;Override
+            public Bitmap getBitmap(String url) {
+                return cache.get(url);
+            }
+
+            &#64;Override
+            public void putBitmap(String url, Bitmap bitmap) {
+                cache.put(url, bitmap);
+            }
+        });
+    }
+
+    public static synchronized MySingleton getInstance(Context context) {
+        if (mInstance == null) {
+            mInstance = new MySingleton(context);
+        }
+        return mInstance;
+    }
+
+    public RequestQueue getRequestQueue() {
+        if (mRequestQueue == null) {
+            // getApplicationContext() is key, it keeps you from leaking the
+            // Activity or BroadcastReceiver if someone passes one in.
+            mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
+        }
+        return mRequestQueue;
+    }
+
+    public &lt;T&gt; void addToRequestQueue(Request&lt;T&gt; req) {
+        getRequestQueue().add(req);
+    }
+
+    public ImageLoader getImageLoader() {
+        return mImageLoader;
+    }
+}</pre>
+
+<p>Here are some examples of performing {@code RequestQueue} operations using the singleton
+class:</p>
+
+<pre>
+// Get a RequestQueue
+RequestQueue queue = MySingleton.getInstance(this.getApplicationContext()).
+    getRequestQueue();
+...
+
+// Add a request (in this example, called stringRequest) to your RequestQueue.
+MySingleton.getInstance(this).addToRequestQueue(stringRequest);
+</pre>
diff --git a/docs/html/training/volley/simple.jd b/docs/html/training/volley/simple.jd
new file mode 100644
index 0000000..942c57f
--- /dev/null
+++ b/docs/html/training/volley/simple.jd
@@ -0,0 +1,169 @@
+page.title=Sending a Simple Request
+
+trainingnavtop=true
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<!-- table of contents -->
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#manifest">Add the INTERNET Permission</a></li>
+  <li><a href="#simple">Use newRequestQueue</a></li>
+  <li><a href="#send">Send a Request</a></li>
+  <li><a href="#cancel">Cancel a Request</a></li>
+</ol>
+
+</div>
+</div>
+
+<a class="notice-developers-video wide" href="https://developers.google.com/events/io/sessions/325304728">
+<div>
+    <h3>Video</h3>
+    <p>Volley: Easy, Fast Networking for Android</p>
+</div>
+</a>
+
+<p>At a high level, you use Volley by creating a {@code RequestQueue} and passing it
+{@code Request} objects. The {@code RequestQueue} manages worker threads for running the
+network operations, reading from and writing to the cache, and parsing responses. Requests
+do the parsing of raw responses and Volley takes care of dispatching the parsed response
+back to the main thread for delivery.</p>
+
+<p> This lesson describes how to send a request using the <code>Volley.newRequestQueue</code>
+convenience method, which sets up a {@code RequestQueue} for you.
+See the next lesson,
+<a href="requestqueue.html">Setting Up a RequestQueue</a>, for information on how to set
+up a {@code RequestQueue} yourself.</p>
+
+<p>This lesson also describes how to add a request to a {@code RequestQueue} and cancel a
+request.</p>
+
+<h2 id="manifest">Add the INTERNET Permission</h2>
+
+<p>To use Volley, you must add the
+{@link android.Manifest.permission#INTERNET android.permission.INTERNET} permission
+to your app's manifest. Without this, your app won't be able to connect to the network.</p>
+
+
+<h2 id="simple">Use newRequestQueue</h2>
+
+<p>Volley provides a convenience method <code>Volley.newRequestQueue</code> that sets up a
+{@code RequestQueue} for you, using default values, and starts the queue. For example:</p>
+
+<pre>
+final TextView mTextView = (TextView) findViewById(R.id.text);
+...
+
+// Instantiate the RequestQueue.
+RequestQueue queue = Volley.newRequestQueue(this);
+String url ="http://www.google.com";
+
+// Request a string response from the provided URL.
+StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
+            new Response.Listener<String>() {
+    &#64;Override
+    public void onResponse(String response) {
+        // Display the first 500 characters of the response string.
+        mTextView.setText("Response is: "+ response.substring(0,500));
+    }
+}, new Response.ErrorListener() {
+    &#64;Override
+    public void onErrorResponse(VolleyError error) {
+        mTextView.setText("That didn't work!");
+    }
+});
+// Add the request to the RequestQueue.
+queue.add(stringRequest);
+</pre>
+
+<p>Volley always delivers parsed responses on the main thread. Running on the main thread
+is convenient for populating UI controls with received data, as you can freely modify UI
+controls directly from your response handler, but it's especially critical to many of the
+important semantics provided by the library, particularly related to canceling requests.
+</p>
+
+<p>See <a href="requestqueue.html">Setting Up a RequestQueue</a> for a
+description of how to set up a {@code RequestQueue} yourself, instead of using the
+<code>Volley.newRequestQueue</code> convenience method.</p>
+
+<h2 id="send">Send a Request</h2>
+
+<p>To send a request, you simply construct one and add it to the {@code RequestQueue} with
+{@code add()}, as shown above. Once you add the request it moves through the pipeline,
+gets serviced, and has its raw response parsed and delivered.</p>
+
+<p>When you call {@code add()}, Volley runs one cache processing thread and a pool of
+network dispatch threads. When you add a request to the queue, it is picked up by the cache
+thread and triaged: if the request can be serviced from cache, the cached response is
+parsed on the cache thread and the parsed response is delivered on the main thread. If the
+request cannot be serviced from cache, it is placed on the network queue. The first
+available network thread takes the request from the queue, performs the HTTP transaction,
+parsse the response on the worker thread, writes the response to cache, and posts the parsed
+response back to the main thread for delivery.</p>
+
+<p>Note that expensive operations like blocking I/O and parsing/decoding are done on worker
+threads. You can add a request from any thread, but responses are always delivered on the
+main thread.</p>
+
+<p>Figure 1 illustrates the life of a request:</p>
+
+ <img src="{@docRoot}images/training/volley-request.png"
+  alt="system bars">
+<p class="img-caption"><strong>Figure 1.</strong> Life of a request.</p>
+
+
+<h2 id="cancel">Cancel a Request</h2>
+
+<p>To cancel a request, call {@code cancel()} on your {@code Request} object. Once cancelled,
+Volley guarantees that your response handler will never be called. What this means in
+practice is that you can cancel all of your pending requests in your activity's
+{@link android.app.Activity#onStop onStop()} method and you don't have to litter your
+response handlers with checks for {@code getActivity() == null},
+whether {@code onSaveInstanceState()} has been called already, or other defensive
+boilerplate.</p>
+
+<p>To take advantage of this behavior, you would typically have to
+track all in-flight requests in order to be able to cancel them at the
+appropriate time. There is an easier way: you can associate a tag object with each
+request. You can then use this tag to provide a scope of requests to cancel. For
+example, you can tag all of your requests with the {@link android.app.Activity} they
+are being made on behalf of, and call {@code requestQueue.cancelAll(this)} from
+{@link android.app.Activity#onStop onStop()}.
+Similarly, you could tag all thumbnail image requests in a
+{@link android.support.v4.view.ViewPager} tab with their respective tabs and cancel on swipe
+to make sure that the new tab isn't being held up by requests from another one.</p>
+
+<p>Here is an example that uses a string value for the tag:</p>
+
+<ol>
+<li>Define your tag and add it to your requests.
+<pre>
+public static final String TAG = "MyTag";
+StringRequest stringRequest; // Assume this exists.
+RequestQueue mRequestQueue;  // Assume this exists.
+
+// Set the tag on the request.
+stringRequest.setTag(TAG);
+
+// Add the request to the RequestQueue.
+mRequestQueue.add(stringRequest);</pre>
+</li>
+
+<li>In your activity's {@link android.app.Activity#onStop onStop()} method, cancel all requests that have this tag.
+<pre>
+&#64;Override
+protected void onStop () {
+    super.onStop();
+    if (mRequestQueue != null) {
+        mRequestQueue.cancelAll(TAG);
+    }
+}
+</pre></li></ol>
+
+<p>Take care when canceling requests. If you are depending on your response handler to
+advance a state or kick off another process, you need to account for this. Again, the
+response handler will not be called.
+</p>
diff --git a/docs/image_sources/brand/Google_Play_Store.ai b/docs/image_sources/brand/Google_Play_Store.ai
deleted file mode 100644
index 51f07c6..0000000
--- a/docs/image_sources/brand/Google_Play_Store.ai
+++ /dev/null
Binary files differ
diff --git a/docs/image_sources/training/volley/volley-request.graffle b/docs/image_sources/training/volley/volley-request.graffle
new file mode 100644
index 0000000..1d79b3df
--- /dev/null
+++ b/docs/image_sources/training/volley/volley-request.graffle
@@ -0,0 +1,2259 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>ActiveLayerIndex</key>
+	<integer>0</integer>
+	<key>ApplicationVersion</key>
+	<array>
+		<string>com.omnigroup.OmniGrafflePro</string>
+		<string>139.18.0.187838</string>
+	</array>
+	<key>AutoAdjust</key>
+	<true/>
+	<key>BackgroundGraphic</key>
+	<dict>
+		<key>Bounds</key>
+		<string>{{0, 0}, {576, 733}}</string>
+		<key>Class</key>
+		<string>SolidGraphic</string>
+		<key>ID</key>
+		<integer>2</integer>
+		<key>Style</key>
+		<dict>
+			<key>shadow</key>
+			<dict>
+				<key>Draws</key>
+				<string>NO</string>
+			</dict>
+			<key>stroke</key>
+			<dict>
+				<key>Draws</key>
+				<string>NO</string>
+			</dict>
+		</dict>
+	</dict>
+	<key>BaseZoom</key>
+	<integer>0</integer>
+	<key>CanvasOrigin</key>
+	<string>{0, 0}</string>
+	<key>ColumnAlign</key>
+	<integer>1</integer>
+	<key>ColumnSpacing</key>
+	<real>36</real>
+	<key>CreationDate</key>
+	<string>2014-03-24 22:38:51 +0000</string>
+	<key>Creator</key>
+	<string>Katie McCormick</string>
+	<key>DisplayScale</key>
+	<string>1 0/72 in = 1 0/72 in</string>
+	<key>GraphDocumentVersion</key>
+	<integer>8</integer>
+	<key>GraphicsList</key>
+	<array>
+		<dict>
+			<key>Bounds</key>
+			<string>{{68.798424333456921, 309.90064645900816}, {70.999998092651367, 24}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>Vertical</string>
+			<key>Flow</key>
+			<string>Resize</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>b</key>
+					<string>0</string>
+					<key>g</key>
+					<string>0</string>
+					<key>r</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>1990</integer>
+			<key>Rotation</key>
+			<real>88.863800048828125</real>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Align</key>
+				<integer>0</integer>
+				<key>Text</key>
+				<string>{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720
+
+\f0\fs24 \cf0 cache miss}</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{164.31081962585449, 233}, {59, 24}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>Flow</key>
+			<string>Resize</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>b</key>
+					<string>0</string>
+					<key>g</key>
+					<string>0</string>
+					<key>r</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>1989</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Align</key>
+				<integer>0</integer>
+				<key>Text</key>
+				<string>{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720
+
+\f0\fs24 \cf0 cache hit}</string>
+			</dict>
+			<key>Wrap</key>
+			<string>NO</string>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{109, 567.5}, {72, 24}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FitText</key>
+			<string>YES</string>
+			<key>Flow</key>
+			<string>Resize</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>b</key>
+					<string>0</string>
+					<key>g</key>
+					<string>0</string>
+					<key>r</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Helvetica</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>1987</integer>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Align</key>
+				<integer>0</integer>
+				<key>Text</key>
+				<string>{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720
+
+\f0\fs24 \cf0 round-robin}</string>
+			</dict>
+			<key>Wrap</key>
+			<string>NO</string>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>209</integer>
+			</dict>
+			<key>ID</key>
+			<integer>1986</integer>
+			<key>Points</key>
+			<array>
+				<string>{362.483096786499, 520.41741752624512}</string>
+				<string>{439.59458923339844, 165.16970062255859}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>Legacy</key>
+					<true/>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>213</integer>
+				<key>Info</key>
+				<integer>7</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>209</integer>
+			</dict>
+			<key>ID</key>
+			<integer>1985</integer>
+			<key>Points</key>
+			<array>
+				<string>{362.483096786499, 461.33483529090881}</string>
+				<string>{439.59458923339844, 165.16970062255859}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>Legacy</key>
+					<true/>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>204</integer>
+				<key>Info</key>
+				<integer>7</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>209</integer>
+			</dict>
+			<key>ID</key>
+			<integer>1984</integer>
+			<key>Points</key>
+			<array>
+				<string>{362.48308152770994, 402.2522608306885}</string>
+				<string>{439.59458923339844, 165.16970062255859}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>Legacy</key>
+					<true/>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>203</integer>
+				<key>Info</key>
+				<integer>7</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>209</integer>
+			</dict>
+			<key>ID</key>
+			<integer>1983</integer>
+			<key>Points</key>
+			<array>
+				<string>{294.65540856933592, 227.99996948242188}</string>
+				<string>{381.3049268594778, 164.77353614247437}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>Legacy</key>
+					<true/>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>210</integer>
+				<key>Info</key>
+				<integer>6</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>4</integer>
+				<key>Info</key>
+				<integer>6</integer>
+			</dict>
+			<key>ID</key>
+			<integer>1982</integer>
+			<key>Points</key>
+			<array>
+				<string>{94.216227905273399, 165.16970062255859}</string>
+				<string>{92.966230102539043, 227.99999237060547}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>Legacy</key>
+					<true/>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>157</integer>
+				<key>Info</key>
+				<integer>5</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>213</integer>
+				<key>Info</key>
+				<integer>9</integer>
+			</dict>
+			<key>ID</key>
+			<integer>1980</integer>
+			<key>Points</key>
+			<array>
+				<string>{179.26087951660151, 462.43391850741568}</string>
+				<string>{224.48312730407713, 514.92974541704564}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>Legacy</key>
+					<true/>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>1973</integer>
+				<key>Info</key>
+				<integer>3</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>204</integer>
+				<key>Info</key>
+				<integer>8</integer>
+			</dict>
+			<key>ID</key>
+			<integer>1979</integer>
+			<key>Points</key>
+			<array>
+				<string>{179.76073189455099, 462.42177007636207}</string>
+				<string>{224.48312730407716, 461.33483529090881}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>Legacy</key>
+					<true/>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>1973</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>203</integer>
+				<key>Info</key>
+				<integer>8</integer>
+			</dict>
+			<key>ID</key>
+			<integer>1978</integer>
+			<key>Points</key>
+			<array>
+				<string>{179.26087951660151, 462.43391850741568}</string>
+				<string>{224.48311204528804, 402.2522608306885}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>Legacy</key>
+					<true/>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>1973</integer>
+				<key>Info</key>
+				<integer>3</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>1973</integer>
+			</dict>
+			<key>ID</key>
+			<integer>1975</integer>
+			<key>Points</key>
+			<array>
+				<string>{92.966230102539043, 286.16969299316406}</string>
+				<string>{92.966239929199205, 356.41910654608148}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>Legacy</key>
+					<true/>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>4</integer>
+				<key>Info</key>
+				<integer>5</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>LineGraphic</string>
+			<key>Head</key>
+			<dict>
+				<key>ID</key>
+				<integer>210</integer>
+				<key>Info</key>
+				<integer>8</integer>
+			</dict>
+			<key>ID</key>
+			<integer>1974</integer>
+			<key>Points</key>
+			<array>
+				<string>{150.81081933593748, 257.08484268188477}</string>
+				<string>{236.81081933593748, 257.08481979370117}</string>
+			</array>
+			<key>Style</key>
+			<dict>
+				<key>stroke</key>
+				<dict>
+					<key>HeadArrow</key>
+					<string>FilledArrow</string>
+					<key>Legacy</key>
+					<true/>
+					<key>TailArrow</key>
+					<string>0</string>
+				</dict>
+			</dict>
+			<key>Tail</key>
+			<dict>
+				<key>ID</key>
+				<integer>4</integer>
+				<key>Info</key>
+				<integer>7</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{391, 540.66744232177734}, {137.99996948242188, 23.499996185302734}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>b</key>
+					<string>0</string>
+					<key>g</key>
+					<string>0</string>
+					<key>r</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Roboto-BoldCondensed</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>214</integer>
+			<key>Magnets</key>
+			<array>
+				<string>{1, 1}</string>
+				<string>{1, -1}</string>
+				<string>{-1, -1}</string>
+				<string>{-1, 1}</string>
+				<string>{0, 1}</string>
+				<string>{0, -1}</string>
+				<string>{1, 0}</string>
+				<string>{-1, 0}</string>
+				<string>{-0.5, -0.233518}</string>
+				<string>{-0.49144199999999999, 0.26006299999999999}</string>
+				<string>{0.50711799999999996, -0.22408600000000001}</string>
+				<string>{0.50711799999999996, 0.267179}</string>
+				<string>{-0.27431, -0.474028}</string>
+				<string>{0.27977999999999997, -0.47847800000000001}</string>
+				<string>{0.29393799999999998, 0.54304399999999997}</string>
+				<string>{-0.28623199999999999, 0.55380399999999996}</string>
+			</array>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.2</string>
+						<key>g</key>
+						<string>0.733333</string>
+						<key>r</key>
+						<string>1</string>
+					</dict>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>a</key>
+						<string>0.35</string>
+						<key>b</key>
+						<string>0.328823</string>
+						<key>g</key>
+						<string>0.328823</string>
+						<key>r</key>
+						<string>0.328823</string>
+					</dict>
+					<key>Fuzziness</key>
+					<real>1.5349206924438477</real>
+					<key>ShadowVector</key>
+					<string>{0, 1}</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.131021</string>
+						<key>g</key>
+						<string>0.363196</string>
+						<key>r</key>
+						<string>0.725948</string>
+					</dict>
+					<key>CornerRadius</key>
+					<real>2</real>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\b\fs24 \cf0 network threads}</string>
+				<key>VerticalPad</key>
+				<integer>0</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{224.48312730407713, 508.66741943359375}, {137.99996948242188, 23.499996185302734}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>b</key>
+					<string>0</string>
+					<key>g</key>
+					<string>0</string>
+					<key>r</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Roboto-BoldCondensed</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>213</integer>
+			<key>Magnets</key>
+			<array>
+				<string>{1, 1}</string>
+				<string>{1, -1}</string>
+				<string>{-1, -1}</string>
+				<string>{-1, 1}</string>
+				<string>{0, 1}</string>
+				<string>{0, -1}</string>
+				<string>{1, 0}</string>
+				<string>{-1, 0}</string>
+				<string>{-0.5, -0.233518}</string>
+				<string>{-0.49144199999999999, 0.26006299999999999}</string>
+				<string>{0.50711799999999996, -0.22408600000000001}</string>
+				<string>{0.50711799999999996, 0.267179}</string>
+				<string>{-0.27431, -0.474028}</string>
+				<string>{0.27977999999999997, -0.47847800000000001}</string>
+				<string>{0.29393799999999998, 0.54304399999999997}</string>
+				<string>{-0.28623199999999999, 0.55380399999999996}</string>
+			</array>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.2</string>
+						<key>g</key>
+						<string>0.733333</string>
+						<key>r</key>
+						<string>1</string>
+					</dict>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>a</key>
+						<string>0.35</string>
+						<key>b</key>
+						<string>0.328823</string>
+						<key>g</key>
+						<string>0.328823</string>
+						<key>r</key>
+						<string>0.328823</string>
+					</dict>
+					<key>Fuzziness</key>
+					<real>1.5349206924438477</real>
+					<key>ShadowVector</key>
+					<string>{0, 1}</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.131021</string>
+						<key>g</key>
+						<string>0.363196</string>
+						<key>r</key>
+						<string>0.725948</string>
+					</dict>
+					<key>CornerRadius</key>
+					<real>2</real>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\b\fs24 \cf0 HTTP...}</string>
+				<key>VerticalPad</key>
+				<integer>0</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{23.966230102539043, 498}, {137.99998474121094, 58.169700622558594}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>b</key>
+					<string>0</string>
+					<key>g</key>
+					<string>0</string>
+					<key>r</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Roboto-BoldCondensed</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>212</integer>
+			<key>Magnets</key>
+			<array>
+				<string>{1, 1}</string>
+				<string>{1, -1}</string>
+				<string>{-1, -1}</string>
+				<string>{-1, 1}</string>
+				<string>{0, 1}</string>
+				<string>{0, -1}</string>
+				<string>{1, 0}</string>
+				<string>{-1, 0}</string>
+				<string>{-0.5, -0.233518}</string>
+				<string>{-0.49144199999999999, 0.26006299999999999}</string>
+				<string>{0.50711799999999996, -0.22408600000000001}</string>
+				<string>{0.50711799999999996, 0.267179}</string>
+				<string>{-0.27431, -0.474028}</string>
+				<string>{0.27977999999999997, -0.47847800000000001}</string>
+				<string>{0.29393799999999998, 0.54304399999999997}</string>
+				<string>{-0.28623199999999999, 0.55380399999999996}</string>
+			</array>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.2</string>
+						<key>g</key>
+						<string>0.733333</string>
+						<key>r</key>
+						<string>1</string>
+					</dict>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>a</key>
+						<string>0.35</string>
+						<key>b</key>
+						<string>0.328823</string>
+						<key>g</key>
+						<string>0.328823</string>
+						<key>r</key>
+						<string>0.328823</string>
+					</dict>
+					<key>Fuzziness</key>
+					<real>1.5349206924438477</real>
+					<key>ShadowVector</key>
+					<string>{0, 1}</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.131021</string>
+						<key>g</key>
+						<string>0.363196</string>
+						<key>r</key>
+						<string>0.725948</string>
+					</dict>
+					<key>CornerRadius</key>
+					<real>2</real>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\b\fs24 \cf0 Request dequeued by NetworkDispatcher}</string>
+				<key>VerticalPad</key>
+				<integer>0</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{23.966245361328106, 432.24999237060547}, {137.99998474121094, 58.169700622558594}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>b</key>
+					<string>0</string>
+					<key>g</key>
+					<string>0</string>
+					<key>r</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Roboto-BoldCondensed</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>211</integer>
+			<key>Magnets</key>
+			<array>
+				<string>{1, 1}</string>
+				<string>{1, -1}</string>
+				<string>{-1, -1}</string>
+				<string>{-1, 1}</string>
+				<string>{0, 1}</string>
+				<string>{0, -1}</string>
+				<string>{1, 0}</string>
+				<string>{-1, 0}</string>
+				<string>{-0.5, -0.233518}</string>
+				<string>{-0.49144199999999999, 0.26006299999999999}</string>
+				<string>{0.50711799999999996, -0.22408600000000001}</string>
+				<string>{0.50711799999999996, 0.267179}</string>
+				<string>{-0.27431, -0.474028}</string>
+				<string>{0.27977999999999997, -0.47847800000000001}</string>
+				<string>{0.29393799999999998, 0.54304399999999997}</string>
+				<string>{-0.28623199999999999, 0.55380399999999996}</string>
+			</array>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.2</string>
+						<key>g</key>
+						<string>0.733333</string>
+						<key>r</key>
+						<string>1</string>
+					</dict>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>a</key>
+						<string>0.35</string>
+						<key>b</key>
+						<string>0.328823</string>
+						<key>g</key>
+						<string>0.328823</string>
+						<key>r</key>
+						<string>0.328823</string>
+					</dict>
+					<key>Fuzziness</key>
+					<real>1.5349206924438477</real>
+					<key>ShadowVector</key>
+					<string>{0, 1}</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.131021</string>
+						<key>g</key>
+						<string>0.363196</string>
+						<key>r</key>
+						<string>0.725948</string>
+					</dict>
+					<key>CornerRadius</key>
+					<real>2</real>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\b\fs24 \cf0 Request dequeued by NetworkDispatcher}</string>
+				<key>VerticalPad</key>
+				<integer>0</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{236.81081933593748, 227.99996948242188}, {115.68917846679688, 58.169700622558594}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>b</key>
+					<string>0</string>
+					<key>g</key>
+					<string>0</string>
+					<key>r</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Roboto-BoldCondensed</string>
+				<key>Size</key>
+				<real>10</real>
+			</dict>
+			<key>ID</key>
+			<integer>210</integer>
+			<key>Magnets</key>
+			<array>
+				<string>{1, 1}</string>
+				<string>{1, -1}</string>
+				<string>{-1, -1}</string>
+				<string>{-1, 1}</string>
+				<string>{0, 1}</string>
+				<string>{0, -1}</string>
+				<string>{1, 0}</string>
+				<string>{-1, 0}</string>
+				<string>{-0.5, -0.233518}</string>
+				<string>{-0.49144199999999999, 0.26006299999999999}</string>
+				<string>{0.50711799999999996, -0.22408600000000001}</string>
+				<string>{0.50711799999999996, 0.267179}</string>
+				<string>{-0.27431, -0.474028}</string>
+				<string>{0.27977999999999997, -0.47847800000000001}</string>
+				<string>{0.29393799999999998, 0.54304399999999997}</string>
+				<string>{-0.28623199999999999, 0.55380399999999996}</string>
+			</array>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0</string>
+						<key>g</key>
+						<string>0.8</string>
+						<key>r</key>
+						<string>0.6</string>
+					</dict>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>a</key>
+						<string>0.35</string>
+						<key>b</key>
+						<string>0.328823</string>
+						<key>g</key>
+						<string>0.328823</string>
+						<key>r</key>
+						<string>0.328823</string>
+					</dict>
+					<key>Fuzziness</key>
+					<real>1.5349206924438477</real>
+					<key>ShadowVector</key>
+					<string>{0, 1}</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0</string>
+						<key>g</key>
+						<string>0.6</string>
+						<key>r</key>
+						<string>0.4</string>
+					</dict>
+					<key>CornerRadius</key>
+					<real>2</real>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\b\fs24 \cf0 Request read from cache and parsed}</string>
+				<key>VerticalPad</key>
+				<integer>0</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{380.5, 107}, {118.18917846679688, 58.169700622558594}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>b</key>
+					<string>0</string>
+					<key>g</key>
+					<string>0</string>
+					<key>r</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Roboto-BoldCondensed</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>209</integer>
+			<key>Magnets</key>
+			<array>
+				<string>{1, 1}</string>
+				<string>{1, -1}</string>
+				<string>{-1, -1}</string>
+				<string>{-1, 1}</string>
+				<string>{0, 1}</string>
+				<string>{0, -1}</string>
+				<string>{1, 0}</string>
+				<string>{-1, 0}</string>
+				<string>{-0.5, -0.233518}</string>
+				<string>{-0.49144199999999999, 0.26006299999999999}</string>
+				<string>{0.50711799999999996, -0.22408600000000001}</string>
+				<string>{0.50711799999999996, 0.267179}</string>
+				<string>{-0.27431, -0.474028}</string>
+				<string>{0.27977999999999997, -0.47847800000000001}</string>
+				<string>{0.29393799999999998, 0.54304399999999997}</string>
+				<string>{-0.28623199999999999, 0.55380399999999996}</string>
+			</array>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.898039</string>
+						<key>g</key>
+						<string>0.709804</string>
+						<key>r</key>
+						<string>0.2</string>
+					</dict>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>a</key>
+						<string>0.35</string>
+						<key>b</key>
+						<string>0.328823</string>
+						<key>g</key>
+						<string>0.328823</string>
+						<key>r</key>
+						<string>0.328823</string>
+					</dict>
+					<key>Fuzziness</key>
+					<real>1.5349206924438477</real>
+					<key>ShadowVector</key>
+					<string>{0, 1}</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.93512</string>
+						<key>g</key>
+						<string>0.472602</string>
+						<key>r</key>
+						<string>0.333854</string>
+					</dict>
+					<key>CornerRadius</key>
+					<real>2</real>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\b\fs24 \cf0 Parsed response delivered on main thread}</string>
+				<key>VerticalPad</key>
+				<integer>0</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{391, 508.66740417480469}, {137.99996948242188, 23.499996185302734}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>b</key>
+					<string>0</string>
+					<key>g</key>
+					<string>0</string>
+					<key>r</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Roboto-BoldCondensed</string>
+				<key>Size</key>
+				<real>10</real>
+			</dict>
+			<key>ID</key>
+			<integer>207</integer>
+			<key>Magnets</key>
+			<array>
+				<string>{1, 1}</string>
+				<string>{1, -1}</string>
+				<string>{-1, -1}</string>
+				<string>{-1, 1}</string>
+				<string>{0, 1}</string>
+				<string>{0, -1}</string>
+				<string>{1, 0}</string>
+				<string>{-1, 0}</string>
+				<string>{-0.5, -0.233518}</string>
+				<string>{-0.49144199999999999, 0.26006299999999999}</string>
+				<string>{0.50711799999999996, -0.22408600000000001}</string>
+				<string>{0.50711799999999996, 0.267179}</string>
+				<string>{-0.27431, -0.474028}</string>
+				<string>{0.27977999999999997, -0.47847800000000001}</string>
+				<string>{0.29393799999999998, 0.54304399999999997}</string>
+				<string>{-0.28623199999999999, 0.55380399999999996}</string>
+			</array>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0</string>
+						<key>g</key>
+						<string>0.8</string>
+						<key>r</key>
+						<string>0.6</string>
+					</dict>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>a</key>
+						<string>0.35</string>
+						<key>b</key>
+						<string>0.328823</string>
+						<key>g</key>
+						<string>0.328823</string>
+						<key>r</key>
+						<string>0.328823</string>
+					</dict>
+					<key>Fuzziness</key>
+					<real>1.5349206924438477</real>
+					<key>ShadowVector</key>
+					<string>{0, 1}</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0</string>
+						<key>g</key>
+						<string>0.6</string>
+						<key>r</key>
+						<string>0.4</string>
+					</dict>
+					<key>CornerRadius</key>
+					<real>2</real>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\b\fs24 \cf0 cache thread}</string>
+				<key>VerticalPad</key>
+				<integer>0</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{391, 476.6673583984375}, {137.99996948242188, 23.499980926513672}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>b</key>
+					<string>0</string>
+					<key>g</key>
+					<string>0</string>
+					<key>r</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Roboto-BoldCondensed</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>206</integer>
+			<key>Magnets</key>
+			<array>
+				<string>{1, 1}</string>
+				<string>{1, -1}</string>
+				<string>{-1, -1}</string>
+				<string>{-1, 1}</string>
+				<string>{0, 1}</string>
+				<string>{0, -1}</string>
+				<string>{1, 0}</string>
+				<string>{-1, 0}</string>
+				<string>{-0.5, -0.233518}</string>
+				<string>{-0.49144199999999999, 0.26006299999999999}</string>
+				<string>{0.50711799999999996, -0.22408600000000001}</string>
+				<string>{0.50711799999999996, 0.267179}</string>
+				<string>{-0.27431, -0.474028}</string>
+				<string>{0.27977999999999997, -0.47847800000000001}</string>
+				<string>{0.29393799999999998, 0.54304399999999997}</string>
+				<string>{-0.28623199999999999, 0.55380399999999996}</string>
+			</array>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.898039</string>
+						<key>g</key>
+						<string>0.709804</string>
+						<key>r</key>
+						<string>0.2</string>
+					</dict>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>a</key>
+						<string>0.35</string>
+						<key>b</key>
+						<string>0.328823</string>
+						<key>g</key>
+						<string>0.328823</string>
+						<key>r</key>
+						<string>0.328823</string>
+					</dict>
+					<key>Fuzziness</key>
+					<real>1.5349206924438477</real>
+					<key>ShadowVector</key>
+					<string>{0, 1}</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.93512</string>
+						<key>g</key>
+						<string>0.472602</string>
+						<key>r</key>
+						<string>0.333854</string>
+					</dict>
+					<key>CornerRadius</key>
+					<real>2</real>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\b\fs24 \cf0 main thread}</string>
+				<key>VerticalPad</key>
+				<integer>0</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{224.48312730407713, 421.74998497962952}, {137.99996948242188, 79.169700622558594}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>b</key>
+					<string>0</string>
+					<key>g</key>
+					<string>0</string>
+					<key>r</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Roboto-BoldCondensed</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>204</integer>
+			<key>Magnets</key>
+			<array>
+				<string>{1, 1}</string>
+				<string>{1, -1}</string>
+				<string>{-1, -1}</string>
+				<string>{-1, 1}</string>
+				<string>{0, 1}</string>
+				<string>{0, -1}</string>
+				<string>{1, 0}</string>
+				<string>{-1, 0}</string>
+				<string>{-0.5, -0.233518}</string>
+				<string>{-0.49144199999999999, 0.26006299999999999}</string>
+				<string>{0.50711799999999996, -0.22408600000000001}</string>
+				<string>{0.50711799999999996, 0.267179}</string>
+				<string>{-0.27431, -0.474028}</string>
+				<string>{0.27977999999999997, -0.47847800000000001}</string>
+				<string>{0.29393799999999998, 0.54304399999999997}</string>
+				<string>{-0.28623199999999999, 0.55380399999999996}</string>
+			</array>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.2</string>
+						<key>g</key>
+						<string>0.733333</string>
+						<key>r</key>
+						<string>1</string>
+					</dict>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>a</key>
+						<string>0.35</string>
+						<key>b</key>
+						<string>0.328823</string>
+						<key>g</key>
+						<string>0.328823</string>
+						<key>r</key>
+						<string>0.328823</string>
+					</dict>
+					<key>Fuzziness</key>
+					<real>1.5349206924438477</real>
+					<key>ShadowVector</key>
+					<string>{0, 1}</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.131021</string>
+						<key>g</key>
+						<string>0.363196</string>
+						<key>r</key>
+						<string>0.725948</string>
+					</dict>
+					<key>CornerRadius</key>
+					<real>2</real>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\b\fs24 \cf0 HTTP transaction, response parse, cache write (if applicable)}</string>
+				<key>VerticalPad</key>
+				<integer>0</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{224.48311204528807, 390.50226273803713}, {137.99996948242188, 23.499996185302734}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>b</key>
+					<string>0</string>
+					<key>g</key>
+					<string>0</string>
+					<key>r</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Roboto-BoldCondensed</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>203</integer>
+			<key>Magnets</key>
+			<array>
+				<string>{1, 1}</string>
+				<string>{1, -1}</string>
+				<string>{-1, -1}</string>
+				<string>{-1, 1}</string>
+				<string>{0, 1}</string>
+				<string>{0, -1}</string>
+				<string>{1, 0}</string>
+				<string>{-1, 0}</string>
+				<string>{-0.5, -0.233518}</string>
+				<string>{-0.49144199999999999, 0.26006299999999999}</string>
+				<string>{0.50711799999999996, -0.22408600000000001}</string>
+				<string>{0.50711799999999996, 0.267179}</string>
+				<string>{-0.27431, -0.474028}</string>
+				<string>{0.27977999999999997, -0.47847800000000001}</string>
+				<string>{0.29393799999999998, 0.54304399999999997}</string>
+				<string>{-0.28623199999999999, 0.55380399999999996}</string>
+			</array>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.2</string>
+						<key>g</key>
+						<string>0.733333</string>
+						<key>r</key>
+						<string>1</string>
+					</dict>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>a</key>
+						<string>0.35</string>
+						<key>b</key>
+						<string>0.328823</string>
+						<key>g</key>
+						<string>0.328823</string>
+						<key>r</key>
+						<string>0.328823</string>
+					</dict>
+					<key>Fuzziness</key>
+					<real>1.5349206924438477</real>
+					<key>ShadowVector</key>
+					<string>{0, 1}</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.131021</string>
+						<key>g</key>
+						<string>0.363196</string>
+						<key>r</key>
+						<string>0.725948</string>
+					</dict>
+					<key>CornerRadius</key>
+					<real>2</real>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\b\fs24 \cf0 HTTP...}</string>
+				<key>VerticalPad</key>
+				<integer>0</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{23.966245361328106, 366.5}, {137.99998474121094, 58.169700622558594}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>b</key>
+					<string>0</string>
+					<key>g</key>
+					<string>0</string>
+					<key>r</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Roboto-BoldCondensed</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>200</integer>
+			<key>Magnets</key>
+			<array>
+				<string>{1, 1}</string>
+				<string>{1, -1}</string>
+				<string>{-1, -1}</string>
+				<string>{-1, 1}</string>
+				<string>{0, 1}</string>
+				<string>{0, -1}</string>
+				<string>{1, 0}</string>
+				<string>{-1, 0}</string>
+				<string>{-0.5, -0.233518}</string>
+				<string>{-0.49144199999999999, 0.26006299999999999}</string>
+				<string>{0.50711799999999996, -0.22408600000000001}</string>
+				<string>{0.50711799999999996, 0.267179}</string>
+				<string>{-0.27431, -0.474028}</string>
+				<string>{0.27977999999999997, -0.47847800000000001}</string>
+				<string>{0.29393799999999998, 0.54304399999999997}</string>
+				<string>{-0.28623199999999999, 0.55380399999999996}</string>
+			</array>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.2</string>
+						<key>g</key>
+						<string>0.733333</string>
+						<key>r</key>
+						<string>1</string>
+					</dict>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>a</key>
+						<string>0.35</string>
+						<key>b</key>
+						<string>0.328823</string>
+						<key>g</key>
+						<string>0.328823</string>
+						<key>r</key>
+						<string>0.328823</string>
+					</dict>
+					<key>Fuzziness</key>
+					<real>1.5349206924438477</real>
+					<key>ShadowVector</key>
+					<string>{0, 1}</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.131021</string>
+						<key>g</key>
+						<string>0.363196</string>
+						<key>r</key>
+						<string>0.725948</string>
+					</dict>
+					<key>CornerRadius</key>
+					<real>2</real>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\b\fs24 \cf0 Request dequeued by NetworkDispatcher}</string>
+				<key>VerticalPad</key>
+				<integer>0</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{35.121640869140606, 227.99999237060547}, {115.68917846679688, 58.169700622558594}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>b</key>
+					<string>0</string>
+					<key>g</key>
+					<string>0</string>
+					<key>r</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Roboto-BoldCondensed</string>
+				<key>Size</key>
+				<real>10</real>
+			</dict>
+			<key>ID</key>
+			<integer>4</integer>
+			<key>Magnets</key>
+			<array>
+				<string>{1, 1}</string>
+				<string>{1, -1}</string>
+				<string>{-1, -1}</string>
+				<string>{-1, 1}</string>
+				<string>{0, 1}</string>
+				<string>{0, -1}</string>
+				<string>{1, 0}</string>
+				<string>{-1, 0}</string>
+				<string>{-0.5, -0.233518}</string>
+				<string>{-0.49144199999999999, 0.26006299999999999}</string>
+				<string>{0.50711799999999996, -0.22408600000000001}</string>
+				<string>{0.50711799999999996, 0.267179}</string>
+				<string>{-0.27431, -0.474028}</string>
+				<string>{0.27977999999999997, -0.47847800000000001}</string>
+				<string>{0.29393799999999998, 0.54304399999999997}</string>
+				<string>{-0.28623199999999999, 0.55380399999999996}</string>
+			</array>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0</string>
+						<key>g</key>
+						<string>0.8</string>
+						<key>r</key>
+						<string>0.6</string>
+					</dict>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>a</key>
+						<string>0.35</string>
+						<key>b</key>
+						<string>0.328823</string>
+						<key>g</key>
+						<string>0.328823</string>
+						<key>r</key>
+						<string>0.328823</string>
+					</dict>
+					<key>Fuzziness</key>
+					<real>1.5349206924438477</real>
+					<key>ShadowVector</key>
+					<string>{0, 1}</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0</string>
+						<key>g</key>
+						<string>0.6</string>
+						<key>r</key>
+						<string>0.4</string>
+					</dict>
+					<key>CornerRadius</key>
+					<real>2</real>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\b\fs24 \cf0 Request dequeued by CacheDispatcher}</string>
+				<key>VerticalPad</key>
+				<integer>0</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Bounds</key>
+			<string>{{35.121638671874962, 107}, {118.18917846679688, 58.169700622558594}}</string>
+			<key>Class</key>
+			<string>ShapedGraphic</string>
+			<key>FontInfo</key>
+			<dict>
+				<key>Color</key>
+				<dict>
+					<key>b</key>
+					<string>0</string>
+					<key>g</key>
+					<string>0</string>
+					<key>r</key>
+					<string>0</string>
+				</dict>
+				<key>Font</key>
+				<string>Roboto-BoldCondensed</string>
+				<key>Size</key>
+				<real>12</real>
+			</dict>
+			<key>ID</key>
+			<integer>157</integer>
+			<key>Magnets</key>
+			<array>
+				<string>{1, 1}</string>
+				<string>{1, -1}</string>
+				<string>{-1, -1}</string>
+				<string>{-1, 1}</string>
+				<string>{0, 1}</string>
+				<string>{0, -1}</string>
+				<string>{1, 0}</string>
+				<string>{-1, 0}</string>
+				<string>{-0.5, -0.233518}</string>
+				<string>{-0.49144199999999999, 0.26006299999999999}</string>
+				<string>{0.50711799999999996, -0.22408600000000001}</string>
+				<string>{0.50711799999999996, 0.267179}</string>
+				<string>{-0.27431, -0.474028}</string>
+				<string>{0.27977999999999997, -0.47847800000000001}</string>
+				<string>{0.29393799999999998, 0.54304399999999997}</string>
+				<string>{-0.28623199999999999, 0.55380399999999996}</string>
+			</array>
+			<key>Shape</key>
+			<string>Rectangle</string>
+			<key>Style</key>
+			<dict>
+				<key>fill</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.898039</string>
+						<key>g</key>
+						<string>0.709804</string>
+						<key>r</key>
+						<string>0.2</string>
+					</dict>
+				</dict>
+				<key>shadow</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>a</key>
+						<string>0.35</string>
+						<key>b</key>
+						<string>0.328823</string>
+						<key>g</key>
+						<string>0.328823</string>
+						<key>r</key>
+						<string>0.328823</string>
+					</dict>
+					<key>Fuzziness</key>
+					<real>1.5349206924438477</real>
+					<key>ShadowVector</key>
+					<string>{0, 1}</string>
+				</dict>
+				<key>stroke</key>
+				<dict>
+					<key>Color</key>
+					<dict>
+						<key>b</key>
+						<string>0.93512</string>
+						<key>g</key>
+						<string>0.472602</string>
+						<key>r</key>
+						<string>0.333854</string>
+					</dict>
+					<key>CornerRadius</key>
+					<real>2</real>
+					<key>Draws</key>
+					<string>NO</string>
+				</dict>
+			</dict>
+			<key>Text</key>
+			<dict>
+				<key>Text</key>
+				<string>{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf190
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\b\fs24 \cf0 Request added to queue in priority order}</string>
+				<key>VerticalPad</key>
+				<integer>0</integer>
+			</dict>
+		</dict>
+		<dict>
+			<key>Class</key>
+			<string>Group</string>
+			<key>Graphics</key>
+			<array>
+				<dict>
+					<key>Bounds</key>
+					<string>{{20.00199264625337, 331.00000000000006}, {145.92853034472586, 25.419100784950434}}</string>
+					<key>Class</key>
+					<string>ShapedGraphic</string>
+					<key>FontInfo</key>
+					<dict>
+						<key>Color</key>
+						<dict>
+							<key>a</key>
+							<string>0.65</string>
+							<key>b</key>
+							<string>0</string>
+							<key>g</key>
+							<string>0</string>
+							<key>r</key>
+							<string>0</string>
+						</dict>
+						<key>Font</key>
+						<string>Roboto-Condensed</string>
+						<key>Size</key>
+						<real>12</real>
+					</dict>
+					<key>ID</key>
+					<integer>1972</integer>
+					<key>Shape</key>
+					<string>Rectangle</string>
+					<key>Style</key>
+					<dict>
+						<key>fill</key>
+						<dict>
+							<key>Draws</key>
+							<string>NO</string>
+						</dict>
+						<key>shadow</key>
+						<dict>
+							<key>Draws</key>
+							<string>NO</string>
+						</dict>
+						<key>stroke</key>
+						<dict>
+							<key>Draws</key>
+							<string>NO</string>
+						</dict>
+					</dict>
+					<key>Text</key>
+					<dict>
+						<key>Pad</key>
+						<integer>2</integer>
+					</dict>
+				</dict>
+				<dict>
+					<key>Bounds</key>
+					<string>{{6.671600341796875, 356.41910654608148}, {172.58927917480466, 212.02962392266838}}</string>
+					<key>Class</key>
+					<string>ShapedGraphic</string>
+					<key>FontInfo</key>
+					<dict>
+						<key>Color</key>
+						<dict>
+							<key>a</key>
+							<string>0.65</string>
+							<key>w</key>
+							<string>0</string>
+						</dict>
+						<key>Font</key>
+						<string>Roboto-BoldCondensed</string>
+						<key>Size</key>
+						<real>12</real>
+					</dict>
+					<key>ID</key>
+					<integer>1973</integer>
+					<key>Magnets</key>
+					<array>
+						<string>{0, 1}</string>
+						<string>{0, -1}</string>
+						<string>{1, 0}</string>
+						<string>{-1, 0}</string>
+					</array>
+					<key>Shape</key>
+					<string>Rectangle</string>
+					<key>Style</key>
+					<dict>
+						<key>fill</key>
+						<dict>
+							<key>Draws</key>
+							<string>NO</string>
+						</dict>
+						<key>shadow</key>
+						<dict>
+							<key>Draws</key>
+							<string>NO</string>
+						</dict>
+						<key>stroke</key>
+						<dict>
+							<key>Color</key>
+							<dict>
+								<key>b</key>
+								<string>0.578326</string>
+								<key>g</key>
+								<string>0.578615</string>
+								<key>r</key>
+								<string>0.578453</string>
+							</dict>
+							<key>CornerRadius</key>
+							<real>5</real>
+							<key>Pattern</key>
+							<integer>1</integer>
+						</dict>
+					</dict>
+					<key>Text</key>
+					<dict>
+						<key>VerticalPad</key>
+						<integer>10</integer>
+					</dict>
+					<key>TextPlacement</key>
+					<integer>0</integer>
+				</dict>
+			</array>
+			<key>ID</key>
+			<integer>1971</integer>
+		</dict>
+	</array>
+	<key>GridInfo</key>
+	<dict/>
+	<key>GuidesLocked</key>
+	<string>NO</string>
+	<key>GuidesVisible</key>
+	<string>YES</string>
+	<key>HPages</key>
+	<integer>1</integer>
+	<key>ImageCounter</key>
+	<integer>1</integer>
+	<key>KeepToScale</key>
+	<false/>
+	<key>Layers</key>
+	<array>
+		<dict>
+			<key>Lock</key>
+			<string>NO</string>
+			<key>Name</key>
+			<string>Layer 1</string>
+			<key>Print</key>
+			<string>YES</string>
+			<key>View</key>
+			<string>YES</string>
+		</dict>
+	</array>
+	<key>LayoutInfo</key>
+	<dict>
+		<key>Animate</key>
+		<string>NO</string>
+		<key>circoMinDist</key>
+		<real>18</real>
+		<key>circoSeparation</key>
+		<real>0.0</real>
+		<key>layoutEngine</key>
+		<string>dot</string>
+		<key>neatoSeparation</key>
+		<real>0.0</real>
+		<key>twopiSeparation</key>
+		<real>0.0</real>
+	</dict>
+	<key>LinksVisible</key>
+	<string>NO</string>
+	<key>MagnetsVisible</key>
+	<string>NO</string>
+	<key>MasterSheets</key>
+	<array/>
+	<key>ModificationDate</key>
+	<string>2014-03-24 23:38:43 +0000</string>
+	<key>Modifier</key>
+	<string>Katie McCormick</string>
+	<key>NotesVisible</key>
+	<string>NO</string>
+	<key>Orientation</key>
+	<integer>2</integer>
+	<key>OriginVisible</key>
+	<string>NO</string>
+	<key>PageBreaks</key>
+	<string>YES</string>
+	<key>PrintInfo</key>
+	<dict>
+		<key>NSBottomMargin</key>
+		<array>
+			<string>float</string>
+			<string>41</string>
+		</array>
+		<key>NSHorizonalPagination</key>
+		<array>
+			<string>coded</string>
+			<string>BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG</string>
+		</array>
+		<key>NSLeftMargin</key>
+		<array>
+			<string>float</string>
+			<string>18</string>
+		</array>
+		<key>NSPaperSize</key>
+		<array>
+			<string>size</string>
+			<string>{612, 792}</string>
+		</array>
+		<key>NSPrintReverseOrientation</key>
+		<array>
+			<string>int</string>
+			<string>0</string>
+		</array>
+		<key>NSRightMargin</key>
+		<array>
+			<string>float</string>
+			<string>18</string>
+		</array>
+		<key>NSTopMargin</key>
+		<array>
+			<string>float</string>
+			<string>18</string>
+		</array>
+	</dict>
+	<key>PrintOnePage</key>
+	<false/>
+	<key>ReadOnly</key>
+	<string>NO</string>
+	<key>RowAlign</key>
+	<integer>1</integer>
+	<key>RowSpacing</key>
+	<real>36</real>
+	<key>SheetTitle</key>
+	<string>Canvas 1</string>
+	<key>SmartAlignmentGuidesActive</key>
+	<string>YES</string>
+	<key>SmartDistanceGuidesActive</key>
+	<string>YES</string>
+	<key>UniqueID</key>
+	<integer>1</integer>
+	<key>UseEntirePage</key>
+	<false/>
+	<key>VPages</key>
+	<integer>1</integer>
+	<key>WindowInfo</key>
+	<dict>
+		<key>CurrentSheet</key>
+		<integer>0</integer>
+		<key>ExpandedCanvases</key>
+		<array>
+			<dict>
+				<key>name</key>
+				<string>Canvas 1</string>
+			</dict>
+		</array>
+		<key>Frame</key>
+		<string>{{159, 135}, {899, 874}}</string>
+		<key>ListView</key>
+		<true/>
+		<key>OutlineWidth</key>
+		<integer>142</integer>
+		<key>RightSidebar</key>
+		<false/>
+		<key>ShowRuler</key>
+		<true/>
+		<key>Sidebar</key>
+		<true/>
+		<key>SidebarWidth</key>
+		<integer>120</integer>
+		<key>VisibleRegion</key>
+		<string>{{-94, -1}, {764, 735}}</string>
+		<key>Zoom</key>
+		<real>1</real>
+		<key>ZoomValues</key>
+		<array>
+			<array>
+				<string>Canvas 1</string>
+				<real>1</real>
+				<real>1</real>
+			</array>
+		</array>
+	</dict>
+</dict>
+</plist>
diff --git a/drm/jni/android_drm_DrmManagerClient.cpp b/drm/jni/android_drm_DrmManagerClient.cpp
index de8531b..d321baf 100644
--- a/drm/jni/android_drm_DrmManagerClient.cpp
+++ b/drm/jni/android_drm_DrmManagerClient.cpp
@@ -233,7 +233,7 @@
 static void android_drm_DrmManagerClient_release(
         JNIEnv* env, jobject thiz, jint uniqueId) {
     ALOGV("release - Enter");
-    DrmManagerClientImpl::remove(uniqueId);
+    getDrmManagerClientImpl(env, thiz)->remove(uniqueId);
     getDrmManagerClientImpl(env, thiz)->setOnInfoListener(uniqueId, NULL);
 
     sp<DrmManagerClientImpl> oldClient = setDrmManagerClientImpl(env, thiz, NULL);
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 06cf253..ef0a411 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -194,6 +194,11 @@
      * while {@link #getAllocationByteCount()} will reflect that of the initial
      * configuration.</p>
      *
+     * <p>Note: This may change this result of hasAlpha(). When converting to 565,
+     * the new bitmap will always be considered opaque. When converting from 565,
+     * the new bitmap will be considered non-opaque, and will respect the value
+     * set by setPremultiplied().</p>
+     *
      * <p>WARNING: This method should NOT be called on a bitmap currently used
      * by the view system. It does not make guarantees about how the underlying
      * pixel buffer is remapped to the new config, just that the allocation is
@@ -217,7 +222,8 @@
             throw new IllegalStateException("native-backed bitmaps may not be reconfigured");
         }
 
-        nativeReconfigure(mNativeBitmap, width, height, config.nativeInt, mBuffer.length);
+        nativeReconfigure(mNativeBitmap, width, height, config.nativeInt, mBuffer.length,
+                mIsPremultiplied);
         mWidth = width;
         mHeight = height;
     }
@@ -1586,7 +1592,8 @@
     private static native void nativeDestructor(long nativeBitmap);
     private static native boolean nativeRecycle(long nativeBitmap);
     private static native void nativeReconfigure(long nativeBitmap, int width, int height,
-                                                 int config, int allocSize);
+                                                 int config, int allocSize,
+                                                 boolean isPremultiplied);
 
     private static native boolean nativeCompress(long nativeBitmap, int format,
                                             int quality, OutputStream stream,
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index c20502f..bc20ea5 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -260,7 +260,11 @@
         public boolean inScaled;
 
         /**
-         * If this is set to true, then the resulting bitmap will allocate its
+         * @deprecated As of {@link android.os.Build.VERSION_CODES#L}, this is
+         * ignored.
+         *
+         * In {@link android.os.Build.VERSION_CODES#KITKAT} and below, if this
+         * is set to true, then the resulting bitmap will allocate its
          * pixels such that they can be purged if the system needs to reclaim
          * memory. In that instance, when the pixels need to be accessed again
          * (e.g. the bitmap is drawn, getPixels() is called), they will be
@@ -287,14 +291,20 @@
          * android.graphics.BitmapFactory.Options)} or {@link #decodeFile(String,
          * android.graphics.BitmapFactory.Options)}.</p>
          */
+        @Deprecated
         public boolean inPurgeable;
 
         /**
-         * This field works in conjuction with inPurgeable. If inPurgeable is
-         * false, then this field is ignored. If inPurgeable is true, then this
-         * field determines whether the bitmap can share a reference to the
-         * input data (inputstream, array, etc.) or if it must make a deep copy.
+         * @deprecated As of {@link android.os.Build.VERSION_CODES#L}, this is
+         * ignored.
+         *
+         * In {@link android.os.Build.VERSION_CODES#KITKAT} and below, this
+         * field works in conjuction with inPurgeable. If inPurgeable is false,
+         * then this field is ignored. If inPurgeable is true, then this field
+         * determines whether the bitmap can share a reference to the input
+         * data (inputstream, array, etc.) or if it must make a deep copy.
          */
+        @Deprecated
         public boolean inInputShareable;
 
         /**
diff --git a/graphics/java/android/graphics/Camera.java b/graphics/java/android/graphics/Camera.java
index a40085b..57e0f27 100644
--- a/graphics/java/android/graphics/Camera.java
+++ b/graphics/java/android/graphics/Camera.java
@@ -154,7 +154,7 @@
             getMatrix(mMatrix);
             canvas.concat(mMatrix);
         } else {
-            nativeApplyToCanvas(canvas.getNativeCanvas());
+            nativeApplyToCanvas(canvas.getNativeCanvasWrapper());
         }
     }
 
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index d4ea7a9..13789ca 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -16,11 +16,17 @@
 
 package android.graphics;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.text.GraphicsOperations;
 import android.text.SpannableString;
 import android.text.SpannedString;
 import android.text.TextUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 import javax.microedition.khronos.opengles.GL;
 
 /**
@@ -39,11 +45,11 @@
 public class Canvas {
 
     // assigned in constructors or setBitmap, freed in finalizer
-    private long mNativeCanvas;
+    private long mNativeCanvasWrapper;
 
     /** @hide */
-    public long getNativeCanvas() {
-        return mNativeCanvas;
+    public long getNativeCanvasWrapper() {
+        return mNativeCanvasWrapper;
     }
 
     // may be null
@@ -88,10 +94,10 @@
     private final CanvasFinalizer mFinalizer;
 
     private static final class CanvasFinalizer {
-        private long mNativeCanvas;
+        private long mNativeCanvasWrapper;
 
         public CanvasFinalizer(long nativeCanvas) {
-            mNativeCanvas = nativeCanvas;
+            mNativeCanvasWrapper = nativeCanvas;
         }
 
         @Override
@@ -104,9 +110,9 @@
         }
 
         public void dispose() {
-            if (mNativeCanvas != 0) {
-                finalizer(mNativeCanvas);
-                mNativeCanvas = 0;
+            if (mNativeCanvasWrapper != 0) {
+                finalizer(mNativeCanvasWrapper);
+                mNativeCanvasWrapper = 0;
             }
         }
     }
@@ -120,8 +126,8 @@
     public Canvas() {
         if (!isHardwareAccelerated()) {
             // 0 means no native bitmap
-            mNativeCanvas = initRaster(0);
-            mFinalizer = new CanvasFinalizer(mNativeCanvas);
+            mNativeCanvasWrapper = initRaster(0);
+            mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper);
         } else {
             mFinalizer = null;
         }
@@ -136,13 +142,13 @@
      *
      * @param bitmap Specifies a mutable bitmap for the canvas to draw into.
      */
-    public Canvas(Bitmap bitmap) {
+    public Canvas(@NonNull Bitmap bitmap) {
         if (!bitmap.isMutable()) {
             throw new IllegalStateException("Immutable bitmap passed to Canvas constructor");
         }
         throwIfCannotDraw(bitmap);
-        mNativeCanvas = initRaster(bitmap.ni());
-        mFinalizer = new CanvasFinalizer(mNativeCanvas);
+        mNativeCanvasWrapper = initRaster(bitmap.ni());
+        mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper);
         mBitmap = bitmap;
         mDensity = bitmap.mDensity;
     }
@@ -152,26 +158,12 @@
         if (nativeCanvas == 0) {
             throw new IllegalStateException();
         }
-        mNativeCanvas = nativeCanvas;
-        mFinalizer = new CanvasFinalizer(mNativeCanvas);
+        mNativeCanvasWrapper = initCanvas(nativeCanvas);
+        mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper);
         mDensity = Bitmap.getDefaultDensity();
     }
 
     /**
-     * Replace existing canvas while ensuring that the swap has occurred before
-     * the previous native canvas is unreferenced.
-     */
-    private void safeCanvasSwap(long nativeCanvas, boolean copyState) {
-        final long oldCanvas = mNativeCanvas;
-        mNativeCanvas = nativeCanvas;
-        mFinalizer.mNativeCanvas = nativeCanvas;
-        if (copyState) {
-            copyNativeCanvasState(oldCanvas, mNativeCanvas);
-        }
-        finalizer(oldCanvas);
-    }
-
-    /**
      * Returns null.
      *
      * @deprecated This method is not supported and should not be invoked.
@@ -206,13 +198,13 @@
      * @see #setDensity(int)
      * @see #getDensity()
      */
-    public void setBitmap(Bitmap bitmap) {
+    public void setBitmap(@Nullable Bitmap bitmap) {
         if (isHardwareAccelerated()) {
             throw new RuntimeException("Can't set a bitmap device on a GL canvas");
         }
 
         if (bitmap == null) {
-            safeCanvasSwap(initRaster(0), false);
+            native_setBitmap(mNativeCanvasWrapper, 0, false);
             mDensity = Bitmap.DENSITY_NONE;
         } else {
             if (!bitmap.isMutable()) {
@@ -220,7 +212,7 @@
             }
             throwIfCannotDraw(bitmap);
 
-            safeCanvasSwap(initRaster(bitmap.ni()), true);
+            native_setBitmap(mNativeCanvasWrapper, bitmap.ni(), true);
             mDensity = bitmap.mDensity;
         }
 
@@ -228,6 +220,13 @@
     }
 
     /**
+     * setBitmap() variant for native callers with a raw bitmap handle.
+     */
+    private void setNativeBitmap(long bitmapHandle) {
+        native_setBitmap(mNativeCanvasWrapper, bitmapHandle, false);
+    }
+
+    /**
      * Set the viewport dimensions if this canvas is GL based. If it is not,
      * this method is ignored and no exception is thrown.
      *
@@ -249,21 +248,27 @@
      *
      * @return true if the device that the current layer draws into is opaque
      */
-    public native boolean isOpaque();
+    public boolean isOpaque() {
+        return native_isOpaque(mNativeCanvasWrapper);
+    }
 
     /**
      * Returns the width of the current drawing layer
      *
      * @return the width of the current drawing layer
      */
-    public native int getWidth();
+    public int getWidth() {
+        return native_getWidth(mNativeCanvasWrapper);
+    }
 
     /**
      * Returns the height of the current drawing layer
      *
      * @return the height of the current drawing layer
      */
-    public native int getHeight();
+    public int getHeight() {
+        return native_getHeight(mNativeCanvasWrapper);
+    }
 
     /**
      * <p>Returns the target density of the canvas.  The default density is
@@ -328,6 +333,19 @@
 
     // the SAVE_FLAG constants must match their native equivalents
 
+    /** @hide */
+    @IntDef(flag = true,
+            value = {
+                MATRIX_SAVE_FLAG,
+                CLIP_SAVE_FLAG,
+                HAS_ALPHA_LAYER_SAVE_FLAG,
+                FULL_COLOR_LAYER_SAVE_FLAG,
+                CLIP_TO_LAYER_SAVE_FLAG,
+                ALL_SAVE_FLAG
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Saveflags {}
+
     /** restore the current matrix when restore() is called */
     public static final int MATRIX_SAVE_FLAG = 0x01;
     /** restore the current clip when restore() is called */
@@ -350,7 +368,9 @@
      *
      * @return The value to pass to restoreToCount() to balance this save()
      */
-    public native int save();
+    public int save() {
+        return native_save(mNativeCanvasWrapper, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
+    }
     
     /**
      * Based on saveFlags, can save the current matrix and clip onto a private
@@ -363,7 +383,9 @@
      *                  to save/restore
      * @return The value to pass to restoreToCount() to balance this save()
      */
-    public native int save(int saveFlags);
+    public int save(@Saveflags int saveFlags) {
+        return native_save(mNativeCanvasWrapper, saveFlags);
+    }
 
     /**
      * This behaves the same as save(), but in addition it allocates an
@@ -381,8 +403,9 @@
      * @param saveFlags  see _SAVE_FLAG constants
      * @return       value to pass to restoreToCount() to balance this save()
      */
-    public int saveLayer(RectF bounds, Paint paint, int saveFlags) {
-        return native_saveLayer(mNativeCanvas, bounds,
+    public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint, @Saveflags int saveFlags) {
+        return native_saveLayer(mNativeCanvasWrapper,
+                bounds.left, bounds.top, bounds.right, bounds.bottom,
                 paint != null ? paint.mNativePaint : 0,
                 saveFlags);
     }
@@ -390,16 +413,16 @@
     /**
      * Convenience for saveLayer(bounds, paint, {@link #ALL_SAVE_FLAG})
      */
-    public int saveLayer(RectF bounds, Paint paint) {
+    public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint) {
         return saveLayer(bounds, paint, ALL_SAVE_FLAG);
     }
 
     /**
      * Helper version of saveLayer() that takes 4 values rather than a RectF.
      */
-    public int saveLayer(float left, float top, float right, float bottom, Paint paint,
-            int saveFlags) {
-        return native_saveLayer(mNativeCanvas, left, top, right, bottom,
+    public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint,
+            @Saveflags int saveFlags) {
+        return native_saveLayer(mNativeCanvasWrapper, left, top, right, bottom,
                 paint != null ? paint.mNativePaint : 0,
                 saveFlags);
     }
@@ -407,7 +430,7 @@
     /**
      * Convenience for saveLayer(left, top, right, bottom, paint, {@link #ALL_SAVE_FLAG})
      */
-    public int saveLayer(float left, float top, float right, float bottom, Paint paint) {
+    public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint) {
         return saveLayer(left, top, right, bottom, paint, ALL_SAVE_FLAG);
     }
 
@@ -427,15 +450,17 @@
      * @param saveFlags see _SAVE_FLAG constants
      * @return          value to pass to restoreToCount() to balance this call
      */
-    public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags) {
+    public int saveLayerAlpha(@NonNull RectF bounds, int alpha, @Saveflags int saveFlags) {
         alpha = Math.min(255, Math.max(0, alpha));
-        return native_saveLayerAlpha(mNativeCanvas, bounds, alpha, saveFlags);
+        return native_saveLayerAlpha(mNativeCanvasWrapper,
+                bounds.left, bounds.top, bounds.right, bounds.bottom,
+                alpha, saveFlags);
     }
 
     /**
      * Convenience for saveLayerAlpha(bounds, alpha, {@link #ALL_SAVE_FLAG})
      */
-    public int saveLayerAlpha(RectF bounds, int alpha) {
+    public int saveLayerAlpha(@NonNull RectF bounds, int alpha) {
         return saveLayerAlpha(bounds, alpha, ALL_SAVE_FLAG);
     }
 
@@ -443,8 +468,8 @@
      * Helper for saveLayerAlpha() that takes 4 values instead of a RectF.
      */
     public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha,
-            int saveFlags) {
-        return native_saveLayerAlpha(mNativeCanvas, left, top, right, bottom,
+            @Saveflags int saveFlags) {
+        return native_saveLayerAlpha(mNativeCanvasWrapper, left, top, right, bottom,
                                      alpha, saveFlags);
     }
 
@@ -460,13 +485,17 @@
      * modifications to the matrix/clip state since the last save call. It is
      * an error to call restore() more times than save() was called.
      */
-    public native void restore();
+    public void restore() {
+        native_restore(mNativeCanvasWrapper);
+    }
 
     /**
      * Returns the number of matrix/clip states on the Canvas' private stack.
      * This will equal # save() calls - # restore() calls.
      */
-    public native int getSaveCount();
+    public int getSaveCount() {
+        return native_getSaveCount(mNativeCanvasWrapper);
+    }
 
     /**
      * Efficient way to pop any calls to save() that happened after the save
@@ -481,7 +510,9 @@
      *
      * @param saveCount The save level to restore to.
      */
-    public native void restoreToCount(int saveCount);
+    public void restoreToCount(int saveCount) {
+        native_restoreToCount(mNativeCanvasWrapper, saveCount);
+    }
 
     /**
      * Preconcat the current matrix with the specified translation
@@ -489,7 +520,9 @@
      * @param dx The distance to translate in X
      * @param dy The distance to translate in Y
     */
-    public native void translate(float dx, float dy);
+    public void translate(float dx, float dy) {
+        native_translate(mNativeCanvasWrapper, dx, dy);
+    }
 
     /**
      * Preconcat the current matrix with the specified scale.
@@ -497,7 +530,9 @@
      * @param sx The amount to scale in X
      * @param sy The amount to scale in Y
      */
-    public native void scale(float sx, float sy);
+    public void scale(float sx, float sy) {
+        native_scale(mNativeCanvasWrapper, sx, sy);
+    }
 
     /**
      * Preconcat the current matrix with the specified scale.
@@ -518,7 +553,9 @@
      *
      * @param degrees The amount to rotate, in degrees
      */
-    public native void rotate(float degrees);
+    public void rotate(float degrees) {
+        native_rotate(mNativeCanvasWrapper, degrees);
+    }
 
     /**
      * Preconcat the current matrix with the specified rotation.
@@ -539,7 +576,9 @@
      * @param sx The amount to skew in X
      * @param sy The amount to skew in Y
      */
-    public native void skew(float sx, float sy);
+    public void skew(float sx, float sy) {
+        native_skew(mNativeCanvasWrapper, sx, sy);
+    }
 
     /**
      * Preconcat the current matrix with the specified matrix. If the specified
@@ -547,8 +586,8 @@
      *
      * @param matrix The matrix to preconcatenate with the current matrix
      */
-    public void concat(Matrix matrix) {
-        if (matrix != null) native_concat(mNativeCanvas, matrix.native_instance);
+    public void concat(@Nullable Matrix matrix) {
+        if (matrix != null) native_concat(mNativeCanvasWrapper, matrix.native_instance);
     }
     
     /**
@@ -564,8 +603,8 @@
      *               
      * @see #concat(Matrix) 
      */
-    public void setMatrix(Matrix matrix) {
-        native_setMatrix(mNativeCanvas,
+    public void setMatrix(@Nullable Matrix matrix) {
+        native_setMatrix(mNativeCanvasWrapper,
                          matrix == null ? 0 : matrix.native_instance);
     }
     
@@ -574,8 +613,8 @@
      * the matrix in the canvas, but just returns a copy of it.
      */
     @Deprecated
-    public void getMatrix(Matrix ctm) {
-        native_getCTM(mNativeCanvas, ctm.native_instance);
+    public void getMatrix(@NonNull Matrix ctm) {
+        native_getCTM(mNativeCanvasWrapper, ctm.native_instance);
     }
 
     /**
@@ -583,7 +622,7 @@
      * matrix.
      */
     @Deprecated
-    public final Matrix getMatrix() {
+    public final @NonNull Matrix getMatrix() {
         Matrix m = new Matrix();
         //noinspection deprecation
         getMatrix(m);
@@ -597,8 +636,8 @@
      * @param op How the clip is modified
      * @return true if the resulting clip is non-empty
      */
-    public boolean clipRect(RectF rect, Region.Op op) {
-        return native_clipRect(mNativeCanvas, rect.left, rect.top, rect.right, rect.bottom,
+    public boolean clipRect(@NonNull RectF rect, @NonNull Region.Op op) {
+        return native_clipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
                 op.nativeInt);
     }
 
@@ -610,8 +649,8 @@
      * @param op How the clip is modified
      * @return true if the resulting clip is non-empty
      */
-    public boolean clipRect(Rect rect, Region.Op op) {
-        return native_clipRect(mNativeCanvas, rect.left, rect.top, rect.right, rect.bottom,
+    public boolean clipRect(@NonNull Rect rect, @NonNull Region.Op op) {
+        return native_clipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
                 op.nativeInt);
     }
 
@@ -622,8 +661,11 @@
      * @param rect The rectangle to intersect with the current clip.
      * @return true if the resulting clip is non-empty
      */
-    public native boolean clipRect(RectF rect);
-    
+    public boolean clipRect(@NonNull RectF rect) {
+        return native_clipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
+                Region.Op.INTERSECT.nativeInt);
+    }
+
     /**
      * Intersect the current clip with the specified rectangle, which is
      * expressed in local coordinates.
@@ -631,7 +673,10 @@
      * @param rect The rectangle to intersect with the current clip.
      * @return true if the resulting clip is non-empty
      */
-    public native boolean clipRect(Rect rect);
+    public boolean clipRect(@NonNull Rect rect) {
+        return native_clipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
+                Region.Op.INTERSECT.nativeInt);
+    }
     
     /**
      * Modify the current clip with the specified rectangle, which is
@@ -648,8 +693,9 @@
      * @param op     How the clip is modified
      * @return       true if the resulting clip is non-empty
      */
-    public boolean clipRect(float left, float top, float right, float bottom, Region.Op op) {
-        return native_clipRect(mNativeCanvas, left, top, right, bottom, op.nativeInt);
+    public boolean clipRect(float left, float top, float right, float bottom,
+            @NonNull Region.Op op) {
+        return native_clipRect(mNativeCanvasWrapper, left, top, right, bottom, op.nativeInt);
     }
 
     /**
@@ -665,7 +711,10 @@
      *               clip
      * @return       true if the resulting clip is non-empty
      */
-    public native boolean clipRect(float left, float top, float right, float bottom);
+    public boolean clipRect(float left, float top, float right, float bottom) {
+        return native_clipRect(mNativeCanvasWrapper, left, top, right, bottom,
+                Region.Op.INTERSECT.nativeInt);
+    }
 
     /**
      * Intersect the current clip with the specified rectangle, which is
@@ -680,7 +729,10 @@
      *               clip
      * @return       true if the resulting clip is non-empty
      */
-    public native boolean clipRect(int left, int top, int right, int bottom);
+    public boolean clipRect(int left, int top, int right, int bottom) {
+        return native_clipRect(mNativeCanvasWrapper, left, top, right, bottom,
+                Region.Op.INTERSECT.nativeInt);
+    }
 
     /**
         * Modify the current clip with the specified path.
@@ -689,8 +741,8 @@
      * @param op   How the clip is modified
      * @return     true if the resulting is non-empty
      */
-    public boolean clipPath(Path path, Region.Op op) {
-        return native_clipPath(mNativeCanvas, path.ni(), op.nativeInt);
+    public boolean clipPath(@NonNull Path path, @NonNull Region.Op op) {
+        return native_clipPath(mNativeCanvasWrapper, path.ni(), op.nativeInt);
     }
     
     /**
@@ -699,7 +751,7 @@
      * @param path The path to intersect with the current clip
      * @return     true if the resulting is non-empty
      */
-    public boolean clipPath(Path path) {
+    public boolean clipPath(@NonNull Path path) {
         return clipPath(path, Region.Op.INTERSECT);
     }
     
@@ -713,9 +765,12 @@
      * @param region The region to operate on the current clip, based on op
      * @param op How the clip is modified
      * @return true if the resulting is non-empty
+     *
+     * @deprecated Unlike all other clip calls this API does not respect the
+     *             current matrix. Use {@link #clipRect(Rect)} as an alternative.
      */
-    public boolean clipRegion(Region region, Region.Op op) {
-        return native_clipRegion(mNativeCanvas, region.ni(), op.nativeInt);
+    public boolean clipRegion(@NonNull Region region, @NonNull Region.Op op) {
+        return native_clipRegion(mNativeCanvasWrapper, region.ni(), op.nativeInt);
     }
 
     /**
@@ -727,22 +782,25 @@
      *
      * @param region The region to operate on the current clip, based on op
      * @return true if the resulting is non-empty
+     *
+     * @deprecated Unlike all other clip calls this API does not respect the
+     *             current matrix. Use {@link #clipRect(Rect)} as an alternative.
      */
-    public boolean clipRegion(Region region) {
+    public boolean clipRegion(@NonNull Region region) {
         return clipRegion(region, Region.Op.INTERSECT);
     }
     
-    public DrawFilter getDrawFilter() {
+    public @Nullable DrawFilter getDrawFilter() {
         return mDrawFilter;
     }
     
-    public void setDrawFilter(DrawFilter filter) {
+    public void setDrawFilter(@Nullable DrawFilter filter) {
         long nativeFilter = 0;
         if (filter != null) {
             nativeFilter = filter.mNativeInt;
         }
         mDrawFilter = filter;
-        nativeSetDrawFilter(mNativeCanvas, nativeFilter);
+        nativeSetDrawFilter(mNativeCanvasWrapper, nativeFilter);
     }
 
     public enum EdgeType {
@@ -780,8 +838,9 @@
      * @return      true if the rect (transformed by the canvas' matrix)
      *              does not intersect with the canvas' clip
      */
-    public boolean quickReject(RectF rect, EdgeType type) {
-        return native_quickReject(mNativeCanvas, rect);
+    public boolean quickReject(@NonNull RectF rect, @NonNull EdgeType type) {
+        return native_quickReject(mNativeCanvasWrapper,
+                rect.left, rect.top, rect.right, rect.bottom);
     }
 
     /**
@@ -799,8 +858,8 @@
      * @return            true if the path (transformed by the canvas' matrix)
      *                    does not intersect with the canvas' clip
      */
-    public boolean quickReject(Path path, EdgeType type) {
-        return native_quickReject(mNativeCanvas, path.ni());
+    public boolean quickReject(@NonNull Path path, @NonNull EdgeType type) {
+        return native_quickReject(mNativeCanvasWrapper, path.ni());
     }
 
     /**
@@ -824,8 +883,8 @@
      *                    does not intersect with the canvas' clip
      */
     public boolean quickReject(float left, float top, float right, float bottom,
-                               EdgeType type) {
-        return native_quickReject(mNativeCanvas, left, top, right, bottom);
+            @NonNull EdgeType type) {
+        return native_quickReject(mNativeCanvasWrapper, left, top, right, bottom);
     }
 
     /**
@@ -838,8 +897,8 @@
      *               still return true if the current clip is non-empty.
      * @return true if the current clip is non-empty.
      */
-    public boolean getClipBounds(Rect bounds) {
-        return native_getClipBounds(mNativeCanvas, bounds);
+    public boolean getClipBounds(@Nullable Rect bounds) {
+        return native_getClipBounds(mNativeCanvasWrapper, bounds);
     }
     
     /**
@@ -847,7 +906,7 @@
      *
      * @return the clip bounds, or [0, 0, 0, 0] if the clip is empty.
      */
-    public final Rect getClipBounds() {
+    public final @NonNull Rect getClipBounds() {
         Rect r = new Rect();
         getClipBounds(r);
         return r;
@@ -862,7 +921,7 @@
      * @param b blue component (0..255) of the color to draw onto the canvas
      */
     public void drawRGB(int r, int g, int b) {
-        native_drawRGB(mNativeCanvas, r, g, b);
+        native_drawRGB(mNativeCanvasWrapper, r, g, b);
     }
 
     /**
@@ -875,7 +934,7 @@
      * @param b blue component (0..255) of the color to draw onto the canvas
      */
     public void drawARGB(int a, int r, int g, int b) {
-        native_drawARGB(mNativeCanvas, a, r, g, b);
+        native_drawARGB(mNativeCanvasWrapper, a, r, g, b);
     }
 
     /**
@@ -885,7 +944,7 @@
      * @param color the color to draw onto the canvas
      */
     public void drawColor(int color) {
-        native_drawColor(mNativeCanvas, color);
+        native_drawColor(mNativeCanvasWrapper, color);
     }
 
     /**
@@ -895,8 +954,8 @@
      * @param color the color to draw with
      * @param mode  the porter-duff mode to apply to the color
      */
-    public void drawColor(int color, PorterDuff.Mode mode) {
-        native_drawColor(mNativeCanvas, color, mode.nativeInt);
+    public void drawColor(int color, @NonNull PorterDuff.Mode mode) {
+        native_drawColor(mNativeCanvasWrapper, color, mode.nativeInt);
     }
 
     /**
@@ -906,8 +965,8 @@
      *
      * @param paint The paint used to draw onto the canvas
      */
-    public void drawPaint(Paint paint) {
-        native_drawPaint(mNativeCanvas, paint.mNativePaint);
+    public void drawPaint(@NonNull Paint paint) {
+        native_drawPaint(mNativeCanvasWrapper, paint.mNativePaint);
     }
     
     /**
@@ -926,19 +985,23 @@
      *                 "points" that are drawn is really (count >> 1).
      * @param paint    The paint used to draw the points
      */
-    public native void drawPoints(float[] pts, int offset, int count, Paint paint);
+    public void drawPoints(float[] pts, int offset, int count, @NonNull Paint paint) {
+        native_drawPoints(mNativeCanvasWrapper, pts, offset, count, paint.mNativePaint);
+    }
 
     /**
      * Helper for drawPoints() that assumes you want to draw the entire array
      */
-    public void drawPoints(float[] pts, Paint paint) {
+    public void drawPoints(@NonNull float[] pts, @NonNull Paint paint) {
         drawPoints(pts, 0, pts.length, paint);
     }
 
     /**
      * Helper for drawPoints() for drawing a single point.
      */
-    public native void drawPoint(float x, float y, Paint paint);
+    public void drawPoint(float x, float y, @NonNull Paint paint) {
+        native_drawPoint(mNativeCanvasWrapper, x, y, paint.mNativePaint);
+    }
 
     /**
      * Draw a line segment with the specified start and stop x,y coordinates,
@@ -952,8 +1015,9 @@
      * @param startY The y-coordinate of the start point of the line
      * @param paint  The paint used to draw the line
      */
-    public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) {
-        native_drawLine(mNativeCanvas, startX, startY, stopX, stopY, paint.mNativePaint);
+    public void drawLine(float startX, float startY, float stopX, float stopY,
+            @NonNull Paint paint) {
+        native_drawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.mNativePaint);
     }
 
     /**
@@ -971,9 +1035,11 @@
      *                 (count >> 2).
      * @param paint    The paint used to draw the points
      */
-    public native void drawLines(float[] pts, int offset, int count, Paint paint);
+    public void drawLines(float[] pts, int offset, int count, Paint paint) {
+        native_drawLines(mNativeCanvasWrapper, pts, offset, count, paint.mNativePaint);
+    }
 
-    public void drawLines(float[] pts, Paint paint) {
+    public void drawLines(@NonNull float[] pts, @NonNull Paint paint) {
         drawLines(pts, 0, pts.length, paint);
     }
 
@@ -984,8 +1050,9 @@
      * @param rect  The rect to be drawn
      * @param paint The paint used to draw the rect
      */
-    public void drawRect(RectF rect, Paint paint) {
-        native_drawRect(mNativeCanvas, rect, paint.mNativePaint);
+    public void drawRect(@NonNull RectF rect, @NonNull Paint paint) {
+        native_drawRect(mNativeCanvasWrapper,
+                rect.left, rect.top, rect.right, rect.bottom, paint.mNativePaint);
     }
 
     /**
@@ -995,7 +1062,7 @@
      * @param r        The rectangle to be drawn.
      * @param paint    The paint used to draw the rectangle
      */
-    public void drawRect(Rect r, Paint paint) {
+    public void drawRect(@NonNull Rect r, @NonNull Paint paint) {
         drawRect(r.left, r.top, r.right, r.bottom, paint);
     }
     
@@ -1010,8 +1077,8 @@
      * @param bottom The bottom side of the rectangle to be drawn
      * @param paint  The paint used to draw the rect
      */
-    public void drawRect(float left, float top, float right, float bottom, Paint paint) {
-        native_drawRect(mNativeCanvas, left, top, right, bottom, paint.mNativePaint);
+    public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) {
+        native_drawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.mNativePaint);
     }
 
     /**
@@ -1020,11 +1087,11 @@
      *
      * @param oval The rectangle bounds of the oval to be drawn
      */
-    public void drawOval(RectF oval, Paint paint) {
+    public void drawOval(@NonNull RectF oval, @NonNull Paint paint) {
         if (oval == null) {
             throw new NullPointerException();
         }
-        native_drawOval(mNativeCanvas, oval, paint.mNativePaint);
+        native_drawOval(mNativeCanvasWrapper, oval, paint.mNativePaint);
     }
 
     /**
@@ -1037,8 +1104,8 @@
      * @param radius The radius of the cirle to be drawn
      * @param paint  The paint used to draw the circle
      */
-    public void drawCircle(float cx, float cy, float radius, Paint paint) {
-        native_drawCircle(mNativeCanvas, cx, cy, radius, paint.mNativePaint);
+    public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) {
+        native_drawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.mNativePaint);
     }
 
     /**
@@ -1064,12 +1131,12 @@
                         close it if it is being stroked. This will draw a wedge
      * @param paint      The paint used to draw the arc
      */
-    public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,
-            Paint paint) {
+    public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
+            @NonNull Paint paint) {
         if (oval == null) {
             throw new NullPointerException();
         }
-        native_drawArc(mNativeCanvas, oval, startAngle, sweepAngle,
+        native_drawArc(mNativeCanvasWrapper, oval, startAngle, sweepAngle,
                 useCenter, paint.mNativePaint);
     }
 
@@ -1082,7 +1149,7 @@
      * @param ry    The y-radius of the oval used to round the corners
      * @param paint The paint used to draw the roundRect
      */
-    public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) {
+    public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
         drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint);
     }
 
@@ -1095,8 +1162,8 @@
      * @param paint The paint used to draw the roundRect
      */
     public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
-            Paint paint) {
-        native_drawRoundRect(mNativeCanvas, left, top, right, bottom, rx, ry, paint.mNativePaint);
+            @NonNull Paint paint) {
+        native_drawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry, paint.mNativePaint);
     }
 
     /**
@@ -1106,8 +1173,8 @@
      * @param path  The path to be drawn
      * @param paint The paint used to draw the path
      */
-    public void drawPath(Path path, Paint paint) {
-        native_drawPath(mNativeCanvas, path.ni(), paint.mNativePaint);
+    public void drawPath(@NonNull Path path, @NonNull Paint paint) {
+        native_drawPath(mNativeCanvasWrapper, path.ni(), paint.mNativePaint);
     }
 
     /**
@@ -1133,7 +1200,7 @@
      * 
      * @hide
      */
-    public void drawPatch(NinePatch patch, Rect dst, Paint paint) {
+    public void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, @Nullable Paint paint) {
         patch.drawSoftware(this, dst, paint);
     }
 
@@ -1146,7 +1213,7 @@
      *
      * @hide
      */
-    public void drawPatch(NinePatch patch, RectF dst, Paint paint) {
+    public void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, @Nullable Paint paint) {
         patch.drawSoftware(this, dst, paint);
     }
 
@@ -1169,9 +1236,9 @@
      * @param top    The position of the top side of the bitmap being drawn
      * @param paint  The paint used to draw the bitmap (may be null)
      */
-    public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
+    public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) {
         throwIfCannotDraw(bitmap);
-        native_drawBitmap(mNativeCanvas, bitmap.ni(), left, top,
+        native_drawBitmap(mNativeCanvasWrapper, bitmap.ni(), left, top,
                 paint != null ? paint.mNativePaint : 0, mDensity, mScreenDensity, bitmap.mDensity);
     }
 
@@ -1197,12 +1264,13 @@
      *               to fit into
      * @param paint  May be null. The paint used to draw the bitmap
      */
-    public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) {
+    public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst,
+            @Nullable Paint paint) {
         if (dst == null) {
             throw new NullPointerException();
         }
         throwIfCannotDraw(bitmap);
-        native_drawBitmap(mNativeCanvas, bitmap.ni(), src, dst,
+        native_drawBitmap(mNativeCanvasWrapper, bitmap.ni(), src, dst,
                           paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity);
     }
 
@@ -1228,12 +1296,13 @@
      *               to fit into
      * @param paint  May be null. The paint used to draw the bitmap
      */
-    public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
+    public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst,
+            @Nullable Paint paint) {
         if (dst == null) {
             throw new NullPointerException();
         }
         throwIfCannotDraw(bitmap);
-        native_drawBitmap(mNativeCanvas, bitmap.ni(), src, dst,
+        native_drawBitmap(mNativeCanvasWrapper, bitmap.ni(), src, dst,
                 paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity);
     }
     
@@ -1262,8 +1331,8 @@
      * and copies of pixel data.
      */
     @Deprecated
-    public void drawBitmap(int[] colors, int offset, int stride, float x, float y,
-            int width, int height, boolean hasAlpha, Paint paint) {
+    public void drawBitmap(@NonNull int[] colors, int offset, int stride, float x, float y,
+            int width, int height, boolean hasAlpha, @Nullable Paint paint) {
         // check for valid input
         if (width < 0) {
             throw new IllegalArgumentException("width must be >= 0");
@@ -1285,7 +1354,7 @@
             return;
         }
         // punch down to native for the actual draw
-        native_drawBitmap(mNativeCanvas, colors, offset, stride, x, y, width, height, hasAlpha,
+        native_drawBitmap(mNativeCanvasWrapper, colors, offset, stride, x, y, width, height, hasAlpha,
                 paint != null ? paint.mNativePaint : 0);
     }
 
@@ -1298,8 +1367,8 @@
      * and copies of pixel data.
      */
     @Deprecated
-    public void drawBitmap(int[] colors, int offset, int stride, int x, int y,
-            int width, int height, boolean hasAlpha, Paint paint) {
+    public void drawBitmap(@NonNull int[] colors, int offset, int stride, int x, int y,
+            int width, int height, boolean hasAlpha, @Nullable Paint paint) {
         // call through to the common float version
         drawBitmap(colors, offset, stride, (float)x, (float)y, width, height,
                    hasAlpha, paint);
@@ -1312,8 +1381,8 @@
      * @param matrix The matrix used to transform the bitmap when it is drawn
      * @param paint  May be null. The paint used to draw the bitmap
      */
-    public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
-        nativeDrawBitmapMatrix(mNativeCanvas, bitmap.ni(), matrix.ni(),
+    public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) {
+        nativeDrawBitmapMatrix(mNativeCanvasWrapper, bitmap.ni(), matrix.ni(),
                 paint != null ? paint.mNativePaint : 0);
     }
 
@@ -1352,8 +1421,9 @@
      * @param colorOffset Number of color elements to skip before drawing
      * @param paint  May be null. The paint used to draw the bitmap
      */
-    public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight,
-            float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint) {
+    public void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
+            @NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
+            @Nullable Paint paint) {
         if ((meshWidth | meshHeight | vertOffset | colorOffset) < 0) {
             throw new ArrayIndexOutOfBoundsException();
         }
@@ -1367,7 +1437,7 @@
             // no mul by 2, since we need only 1 color per vertex
             checkRange(colors.length, colorOffset, count);
         }
-        nativeDrawBitmapMesh(mNativeCanvas, bitmap.ni(), meshWidth, meshHeight,
+        nativeDrawBitmapMesh(mNativeCanvasWrapper, bitmap.ni(), meshWidth, meshHeight,
                 verts, vertOffset, colors, colorOffset,
                 paint != null ? paint.mNativePaint : 0);
     }
@@ -1417,9 +1487,10 @@
      * @param indexCount number of entries in the indices array (if not null).
      * @param paint Specifies the shader to use if the texs array is non-null. 
      */
-    public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset,
-            float[] texs, int texOffset, int[] colors, int colorOffset,
-            short[] indices, int indexOffset, int indexCount, Paint paint) {
+    public void drawVertices(@NonNull VertexMode mode, int vertexCount, @NonNull float[] verts,
+            int vertOffset, @Nullable float[] texs, int texOffset, @Nullable int[] colors,
+            int colorOffset, @Nullable short[] indices, int indexOffset, int indexCount,
+            @NonNull Paint paint) {
         checkRange(verts.length, vertOffset, vertexCount);
         if (texs != null) {
             checkRange(texs.length, texOffset, vertexCount);
@@ -1430,7 +1501,7 @@
         if (indices != null) {
             checkRange(indices.length, indexOffset, indexCount);
         }
-        nativeDrawVertices(mNativeCanvas, mode.nativeInt, vertexCount, verts,
+        nativeDrawVertices(mNativeCanvasWrapper, mode.nativeInt, vertexCount, verts,
                 vertOffset, texs, texOffset, colors, colorOffset,
                 indices, indexOffset, indexCount, paint.mNativePaint);
     }
@@ -1444,12 +1515,13 @@
      * @param y     The y-coordinate of the origin of the text being drawn
      * @param paint The paint used for the text (e.g. color, size, style)
      */
-    public void drawText(char[] text, int index, int count, float x, float y, Paint paint) {
+    public void drawText(@NonNull char[] text, int index, int count, float x, float y,
+            @NonNull Paint paint) {
         if ((index | count | (index + count) |
             (text.length - index - count)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        native_drawText(mNativeCanvas, text, index, count, x, y, paint.mBidiFlags,
+        native_drawText(mNativeCanvasWrapper, text, index, count, x, y, paint.mBidiFlags,
                 paint.mNativePaint, paint.mNativeTypeface);
     }
 
@@ -1462,8 +1534,8 @@
      * @param y     The y-coordinate of the origin of the text being drawn
      * @param paint The paint used for the text (e.g. color, size, style)
      */
-    public void drawText(String text, float x, float y, Paint paint) {
-        native_drawText(mNativeCanvas, text, 0, text.length(), x, y, paint.mBidiFlags,
+    public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
+        native_drawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags,
                 paint.mNativePaint, paint.mNativeTypeface);
     }
 
@@ -1478,11 +1550,12 @@
      * @param y     The y-coordinate of the origin of the text being drawn
      * @param paint The paint used for the text (e.g. color, size, style)
      */
-    public void drawText(String text, int start, int end, float x, float y, Paint paint) {
+    public void drawText(@NonNull String text, int start, int end, float x, float y,
+            @NonNull Paint paint) {
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        native_drawText(mNativeCanvas, text, start, end, x, y, paint.mBidiFlags,
+        native_drawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags,
                 paint.mNativePaint, paint.mNativeTypeface);
     }
 
@@ -1499,10 +1572,11 @@
      * @param y        The y-coordinate of origin for where to draw the text
      * @param paint The paint used for the text (e.g. color, size, style)
      */
-    public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
+    public void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
+            @NonNull Paint paint) {
         if (text instanceof String || text instanceof SpannedString ||
             text instanceof SpannableString) {
-            native_drawText(mNativeCanvas, text.toString(), start, end, x, y,
+            native_drawText(mNativeCanvasWrapper, text.toString(), start, end, x, y,
                     paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
         } else if (text instanceof GraphicsOperations) {
             ((GraphicsOperations) text).drawText(this, start, end, x, y,
@@ -1510,7 +1584,7 @@
         } else {
             char[] buf = TemporaryBuffer.obtain(end - start);
             TextUtils.getChars(text, start, end, buf, 0);
-            native_drawText(mNativeCanvas, buf, 0, end - start, x, y,
+            native_drawText(mNativeCanvasWrapper, buf, 0, end - start, x, y,
                     paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
             TemporaryBuffer.recycle(buf);
         }
@@ -1537,8 +1611,8 @@
      * @param paint the paint
      * @hide
      */
-    public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
-            float x, float y, int dir, Paint paint) {
+    public void drawTextRun(@NonNull char[] text, int index, int count, int contextIndex,
+            int contextCount, float x, float y, int dir, @NonNull Paint paint) {
 
         if (text == null) {
             throw new NullPointerException("text is null");
@@ -1553,7 +1627,7 @@
             throw new IllegalArgumentException("unknown dir: " + dir);
         }
 
-        native_drawTextRun(mNativeCanvas, text, index, count,
+        native_drawTextRun(mNativeCanvasWrapper, text, index, count,
                 contextIndex, contextCount, x, y, dir, paint.mNativePaint, paint.mNativeTypeface);
     }
 
@@ -1574,8 +1648,8 @@
      * @param paint the paint
      * @hide
      */
-    public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd,
-            float x, float y, int dir, Paint paint) {
+    public void drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart,
+            int contextEnd, float x, float y, int dir, @NonNull Paint paint) {
 
         if (text == null) {
             throw new NullPointerException("text is null");
@@ -1591,7 +1665,7 @@
 
         if (text instanceof String || text instanceof SpannedString ||
                 text instanceof SpannableString) {
-            native_drawTextRun(mNativeCanvas, text.toString(), start, end,
+            native_drawTextRun(mNativeCanvasWrapper, text.toString(), start, end,
                     contextStart, contextEnd, x, y, flags, paint.mNativePaint, paint.mNativeTypeface);
         } else if (text instanceof GraphicsOperations) {
             ((GraphicsOperations) text).drawTextRun(this, start, end,
@@ -1601,7 +1675,7 @@
             int len = end - start;
             char[] buf = TemporaryBuffer.obtain(contextLen);
             TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
-            native_drawTextRun(mNativeCanvas, buf, start - contextStart, len,
+            native_drawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len,
                     0, contextLen, x, y, flags, paint.mNativePaint, paint.mNativeTypeface);
             TemporaryBuffer.recycle(buf);
         }
@@ -1622,11 +1696,12 @@
      * @param paint    The paint used for the text (e.g. color, size, style)
      */
     @Deprecated
-    public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) {
+    public void drawPosText(@NonNull char[] text, int index, int count, @NonNull float[] pos,
+            @NonNull Paint paint) {
         if (index < 0 || index + count > text.length || count*2 > pos.length) {
             throw new IndexOutOfBoundsException();
         }
-        native_drawPosText(mNativeCanvas, text, index, count, pos,
+        native_drawPosText(mNativeCanvasWrapper, text, index, count, pos,
                 paint.mNativePaint);
     }
 
@@ -1642,11 +1717,11 @@
      * @param paint The paint used for the text (e.g. color, size, style)
      */
     @Deprecated
-    public void drawPosText(String text, float[] pos, Paint paint) {
+    public void drawPosText(@NonNull String text, @NonNull float[] pos, @NonNull Paint paint) {
         if (text.length()*2 > pos.length) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        native_drawPosText(mNativeCanvas, text, pos, paint.mNativePaint);
+        native_drawPosText(mNativeCanvasWrapper, text, pos, paint.mNativePaint);
     }
 
     /**
@@ -1662,12 +1737,12 @@
      *                 the text
      * @param paint    The paint used for the text (e.g. color, size, style)
      */
-    public void drawTextOnPath(char[] text, int index, int count, Path path,
-            float hOffset, float vOffset, Paint paint) {
+    public void drawTextOnPath(@NonNull char[] text, int index, int count, @NonNull Path path,
+            float hOffset, float vOffset, @NonNull Paint paint) {
         if (index < 0 || index + count > text.length) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        native_drawTextOnPath(mNativeCanvas, text, index, count,
+        native_drawTextOnPath(mNativeCanvasWrapper, text, index, count,
                 path.ni(), hOffset, vOffset,
                 paint.mBidiFlags, paint.mNativePaint);
     }
@@ -1685,9 +1760,10 @@
      *                 the text
      * @param paint    The paint used for the text (e.g. color, size, style)
      */
-    public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) {
+    public void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,
+            float vOffset, @NonNull Paint paint) {
         if (text.length() > 0) {
-            native_drawTextOnPath(mNativeCanvas, text, path.ni(), hOffset, vOffset,
+            native_drawTextOnPath(mNativeCanvasWrapper, text, path.ni(), hOffset, vOffset,
                     paint.mBidiFlags, paint.mNativePaint);
         }
     }
@@ -1703,7 +1779,7 @@
      * 
      * @param picture  The picture to be drawn
      */
-    public void drawPicture(Picture picture) {
+    public void drawPicture(@NonNull Picture picture) {
         picture.endRecording();
         int restoreCount = save();
         picture.draw(this);
@@ -1713,7 +1789,7 @@
     /**
      * Draw the picture, stretched to fit into the dst rectangle.
      */
-    public void drawPicture(Picture picture, RectF dst) {
+    public void drawPicture(@NonNull Picture picture, @NonNull RectF dst) {
         save();
         translate(dst.left, dst.top);
         if (picture.getWidth() > 0 && picture.getHeight() > 0) {
@@ -1726,7 +1802,7 @@
     /**
      * Draw the picture, stretched to fit into the dst rectangle.
      */
-    public void drawPicture(Picture picture, Rect dst) {
+    public void drawPicture(@NonNull Picture picture, @NonNull Rect dst) {
         save();
         translate(dst.left, dst.top);
         if (picture.getWidth() > 0 && picture.getHeight() > 0) {
@@ -1761,23 +1837,34 @@
     public static native void freeTextLayoutCaches();
 
     private static native long initRaster(long nativeBitmapOrZero);
-    private static native void copyNativeCanvasState(long nativeSrcCanvas,
-                                                     long nativeDstCanvas);
-    private static native int native_saveLayer(long nativeCanvas,
-                                               RectF bounds,
-                                               long nativePaint,
-                                               int layerFlags);
+    private static native long initCanvas(long canvasHandle);
+    private static native void native_setBitmap(long canvasHandle,
+                                                long bitmapHandle,
+                                                boolean copyState);
+    private static native boolean native_isOpaque(long canvasHandle);
+    private static native int native_getWidth(long canvasHandle);
+    private static native int native_getHeight(long canvasHandle);
+
+    private static native int native_save(long canvasHandle, int saveFlags);
     private static native int native_saveLayer(long nativeCanvas, float l,
                                                float t, float r, float b,
                                                long nativePaint,
                                                int layerFlags);
-    private static native int native_saveLayerAlpha(long nativeCanvas,
-                                                    RectF bounds, int alpha,
-                                                    int layerFlags);
     private static native int native_saveLayerAlpha(long nativeCanvas, float l,
                                                     float t, float r, float b,
                                                     int alpha, int layerFlags);
+    private static native void native_restore(long canvasHandle);
+    private static native void native_restoreToCount(long canvasHandle,
+                                                     int saveCount);
+    private static native int native_getSaveCount(long canvasHandle);
 
+    private static native void native_translate(long canvasHandle,
+                                                float dx, float dy);
+    private static native void native_scale(long canvasHandle,
+                                            float sx, float sy);
+    private static native void native_rotate(long canvasHandle, float degrees);
+    private static native void native_skew(long canvasHandle,
+                                           float sx, float sy);
     private static native void native_concat(long nativeCanvas,
                                              long nativeMatrix);
     private static native void native_setMatrix(long nativeCanvas,
@@ -1799,8 +1886,6 @@
     private static native void native_getCTM(long nativeCanvas,
                                              long nativeMatrix);
     private static native boolean native_quickReject(long nativeCanvas,
-                                                     RectF rect);
-    private static native boolean native_quickReject(long nativeCanvas,
                                                      long nativePath);
     private static native boolean native_quickReject(long nativeCanvas,
                                                      float left, float top,
@@ -1814,11 +1899,17 @@
                                                 int mode);
     private static native void native_drawPaint(long nativeCanvas,
                                                 long nativePaint);
+    private static native void native_drawPoint(long canvasHandle, float x, float y,
+                                                long paintHandle);
+    private static native void native_drawPoints(long canvasHandle, float[] pts,
+                                                 int offset, int count,
+                                                 long paintHandle);
     private static native void native_drawLine(long nativeCanvas, float startX,
                                                float startY, float stopX,
                                                float stopY, long nativePaint);
-    private static native void native_drawRect(long nativeCanvas, RectF rect,
-                                               long nativePaint);
+    private static native void native_drawLines(long canvasHandle, float[] pts,
+                                                int offset, int count,
+                                                long paintHandle);
     private static native void native_drawRect(long nativeCanvas, float left,
                                                float top, float right,
                                                float bottom,
diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java
index 6ff5f4f..befac92 100644
--- a/graphics/java/android/graphics/NinePatch.java
+++ b/graphics/java/android/graphics/NinePatch.java
@@ -164,12 +164,12 @@
     }
 
     void drawSoftware(Canvas canvas, RectF location, Paint paint) {
-        nativeDraw(canvas.getNativeCanvas(), location, mBitmap.ni(), mNativeChunk,
+        nativeDraw(canvas.getNativeCanvasWrapper(), location, mBitmap.ni(), mNativeChunk,
                 paint != null ? paint.mNativePaint : 0, canvas.mDensity, mBitmap.mDensity);
     }
 
     void drawSoftware(Canvas canvas, Rect location, Paint paint) {
-        nativeDraw(canvas.getNativeCanvas(), location, mBitmap.ni(), mNativeChunk,
+        nativeDraw(canvas.getNativeCanvasWrapper(), location, mBitmap.ni(), mNativeChunk,
                 paint != null ? paint.mNativePaint : 0, canvas.mDensity, mBitmap.mDensity);
     }
 
diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java
index a16c099..a021165 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -53,7 +53,7 @@
     public Picture(Picture src) {
         this(nativeConstructor(src != null ? src.mNativePicture : 0), false);
     }
-    
+
     /**
      * To record a picture, call beginRecording() and then draw into the Canvas
      * that is returned. Nothing we appear on screen, but all of the draw
@@ -67,7 +67,7 @@
         mRecordingCanvas = new RecordingCanvas(this, ni);
         return mRecordingCanvas;
     }
-    
+
     /**
      * Call endRecording when the picture is built. After this call, the picture
      * may be drawn, but the canvas that was returned by beginRecording must not
@@ -92,22 +92,25 @@
      * does not reflect (per se) the content of the picture.
      */
     public native int getHeight();
-    
+
     /**
-     * Draw this picture on the canvas. The picture may have the side effect
-     * of changing the matrix and clip of the canvas.
-     * 
+     * Draw this picture on the canvas.
+     * <p>
+     * Prior to {@link android.os.Build.VERSION_CODES#L}, this call could
+     * have the side effect of changing the matrix and clip of the canvas
+     * if this picture had imbalanced saves/restores.
+     *
      * <p>
      * <strong>Note:</strong> This forces the picture to internally call
      * {@link Picture#endRecording()} in order to prepare for playback.
      *
-     * @param canvas  The picture is drawn to this canvas 
+     * @param canvas  The picture is drawn to this canvas
      */
     public void draw(Canvas canvas) {
         if (mRecordingCanvas != null) {
             endRecording();
         }
-        nativeDraw(canvas.getNativeCanvas(), mNativePicture);
+        nativeDraw(canvas.getNativeCanvasWrapper(), mNativePicture);
     }
 
     /**
@@ -119,7 +122,7 @@
      * <p>
      * <strong>Note:</strong> a picture created from an input stream cannot be
      * replayed on a hardware accelerated canvas.
-     * 
+     *
      * @see #writeToStream(java.io.OutputStream)
      * @deprecated The recommended alternative is to not use writeToStream and
      * instead draw the picture into a Bitmap from which you can persist it as
@@ -167,7 +170,7 @@
     final long ni() {
         return mNativePicture;
     }
-    
+
     private Picture(long nativePicture, boolean fromStream) {
         if (nativePicture == 0) {
             throw new RuntimeException();
@@ -187,7 +190,7 @@
     private static native boolean nativeWriteToStream(long nativePicture,
                                            OutputStream stream, byte[] storage);
     private static native void nativeDestructor(long nativePicture);
-    
+
     private static class RecordingCanvas extends Canvas {
         private final Picture mPicture;
 
@@ -195,7 +198,7 @@
             super(nativeCanvas);
             mPicture = pict;
         }
-        
+
         @Override
         public void setBitmap(Bitmap bitmap) {
             throw new RuntimeException(
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
new file mode 100644
index 0000000..968c0ec
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.graphics.drawable;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * AnimatedVectorDrawable can use ObjectAnimator and AnimatorSet to animate
+ * the property of the VectorDrawable.
+ *
+ * @hide
+ */
+public class AnimatedVectorDrawable extends Drawable implements Animatable {
+    private static final String LOGTAG = AnimatedVectorDrawable.class.getSimpleName();
+
+    private static final String ANIMATED_VECTOR = "animated-vector";
+    private static final String TARGET = "target";
+
+    private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
+
+    private final AnimatedVectorDrawableState mAnimatedVectorState;
+
+
+    public AnimatedVectorDrawable() {
+        mAnimatedVectorState = new AnimatedVectorDrawableState(
+                new AnimatedVectorDrawableState(null));
+    }
+
+    private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res,
+            Theme theme) {
+        // TODO: Correctly handle the constant state for AVD.
+        mAnimatedVectorState = new AnimatedVectorDrawableState(state);
+        if (theme != null && canApplyTheme()) {
+            applyTheme(theme);
+        }
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        return null;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        mAnimatedVectorState.mVectorDrawable.draw(canvas);
+        if (isRunning()) {
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
+    }
+
+    @Override
+    public int getAlpha() {
+        return mAnimatedVectorState.mVectorDrawable.getAlpha();
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
+    }
+
+    @Override
+    public int getOpacity() {
+        return mAnimatedVectorState.mVectorDrawable.getOpacity();
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
+    }
+
+    @Override
+    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
+            throws XmlPullParserException, IOException {
+
+        int eventType = parser.getEventType();
+        while (eventType != XmlPullParser.END_DOCUMENT) {
+            if (eventType == XmlPullParser.START_TAG) {
+                final String tagName = parser.getName();
+                if (ANIMATED_VECTOR.equals(tagName)) {
+                    final TypedArray a = obtainAttributes(res, theme, attrs,
+                            R.styleable.AnimatedVectorDrawable);
+                    int drawableRes = a.getResourceId(
+                            R.styleable.AnimatedVectorDrawable_drawable, 0);
+                    if (drawableRes != 0) {
+                        mAnimatedVectorState.mVectorDrawable = (VectorDrawable) res.getDrawable(
+                                drawableRes);
+                    }
+                    a.recycle();
+                } else if (TARGET.equals(tagName)) {
+                    final TypedArray a = obtainAttributes(res, theme, attrs,
+                            R.styleable.AnimatedVectorDrawableTarget);
+                    final String target = a.getString(
+                            R.styleable.AnimatedVectorDrawableTarget_name);
+
+                    int id = a.getResourceId(
+                            R.styleable.AnimatedVectorDrawableTarget_animation, 0);
+                    if (id != 0) {
+                        Animator objectAnimator = AnimatorInflater.loadAnimator(res, theme, id);
+                        setupAnimatorsForTarget(target, objectAnimator);
+                    }
+                    a.recycle();
+                }
+            }
+
+            eventType = parser.next();
+        }
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return super.canApplyTheme() || mAnimatedVectorState != null
+                && mAnimatedVectorState.canApplyTheme();
+    }
+
+    @Override
+    public void applyTheme(Theme t) {
+        super.applyTheme(t);
+
+        final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable;
+        if (vectorDrawable != null && vectorDrawable.canApplyTheme()) {
+            vectorDrawable.applyTheme(t);
+        }
+    }
+
+    private static class AnimatedVectorDrawableState extends ConstantState {
+        int mChangingConfigurations;
+        VectorDrawable mVectorDrawable;
+        ArrayList<Animator> mAnimators;
+
+        public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy) {
+            if (copy != null) {
+                mChangingConfigurations = copy.mChangingConfigurations;
+                // TODO: Make sure the constant state are handled correctly.
+                mVectorDrawable = new VectorDrawable();
+                mAnimators = new ArrayList<Animator>();
+            }
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new AnimatedVectorDrawable(this, null, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new AnimatedVectorDrawable(this, res, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res, Theme theme) {
+            return new AnimatedVectorDrawable(this, res, theme);
+        }
+
+        @Override
+        public int getChangingConfigurations() {
+            return mChangingConfigurations;
+        }
+    }
+
+    private void setupAnimatorsForTarget(String name, Animator animator) {
+        Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name);
+        animator.setTarget(target);
+        mAnimatedVectorState.mAnimators.add(animator);
+        if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+            Log.v(LOGTAG, "add animator  for target " + name + " " + animator);
+        }
+    }
+
+    @Override
+    public boolean isRunning() {
+        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
+        final int size = animators.size();
+        for (int i = 0; i < size; i++) {
+            final Animator animator = animators.get(i);
+            if (animator.isRunning()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void start() {
+        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
+        final int size = animators.size();
+        for (int i = 0; i < size; i++) {
+            final Animator animator = animators.get(i);
+            if (animator.isPaused()) {
+                animator.resume();
+            } else if (!animator.isRunning()) {
+                animator.start();
+            }
+        }
+        invalidateSelf();
+    }
+
+    @Override
+    public void stop() {
+        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
+        final int size = animators.size();
+        for (int i = 0; i < size; i++) {
+            final Animator animator = animators.get(i);
+            animator.pause();
+        }
+    }
+}
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 18e8e52..6a7757b 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -209,7 +209,7 @@
      *         stored bounds of this drawable.
      *
      * @see #copyBounds()
-     * @see #copyBounds(android.graphics.Rect) 
+     * @see #copyBounds(android.graphics.Rect)
      */
     public final Rect getBounds() {
         if (mBounds == ZERO_BOUNDS_RECT) {
@@ -328,8 +328,8 @@
      * that want to support animated drawables.
      *
      * @param cb The client's Callback implementation.
-     * 
-     * @see #getCallback() 
+     *
+     * @see #getCallback()
      */
     public final void setCallback(Callback cb) {
         mCallback = new WeakReference<Callback>(cb);
@@ -338,10 +338,10 @@
     /**
      * Return the current {@link Callback} implementation attached to this
      * Drawable.
-     * 
+     *
      * @return A {@link Callback} instance or null if no callback was set.
-     * 
-     * @see #setCallback(android.graphics.drawable.Drawable.Callback) 
+     *
+     * @see #setCallback(android.graphics.drawable.Drawable.Callback)
      */
     public Callback getCallback() {
         if (mCallback != null) {
@@ -349,15 +349,15 @@
         }
         return null;
     }
-    
+
     /**
      * Use the current {@link Callback} implementation to have this Drawable
      * redrawn.  Does nothing if there is no Callback attached to the
      * Drawable.
      *
      * @see Callback#invalidateDrawable
-     * @see #getCallback() 
-     * @see #setCallback(android.graphics.drawable.Drawable.Callback) 
+     * @see #getCallback()
+     * @see #setCallback(android.graphics.drawable.Drawable.Callback)
      */
     public void invalidateSelf() {
         final Callback callback = getCallback();
@@ -931,7 +931,7 @@
             Rects only to drop them on the floor.
         */
         Rect pad = new Rect();
-        
+
         // Special stuff for compatibility mode: if the target density is not
         // the same as the display density, but the resource -is- the same as
         // the display density, then don't scale it down to the target density.
@@ -1040,6 +1040,8 @@
             drawable = new GradientDrawable();
         } else if (name.equals("vector")) {
             drawable = new VectorDrawable();
+        } else if (name.equals("animated-vector")) {
+            drawable = new AnimatedVectorDrawable();
         } else if (name.equals("scale")) {
             drawable = new ScaleDrawable();
         } else if (name.equals("clip")) {
@@ -1047,7 +1049,7 @@
         } else if (name.equals("rotate")) {
             drawable = new RotateDrawable();
         } else if (name.equals("animated-rotate")) {
-            drawable = new AnimatedRotateDrawable();            
+            drawable = new AnimatedRotateDrawable();
         } else if (name.equals("animation-list")) {
             drawable = new AnimationDrawable();
         } else if (name.equals("inset")) {
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 9a63fa3..c531c22 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -18,6 +18,7 @@
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.Matrix;
 import android.graphics.Paint;
@@ -26,6 +27,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Xml;
@@ -39,8 +41,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
+import java.util.Stack;
 
 /**
  * This lets you create a drawable based on an XML vector graphic It can be
@@ -58,9 +59,26 @@
  * <dd>Used to defined the size of the virtual canvas the paths are drawn on.
  * The size is defined using the attributes <code>android:viewportHeight</code>
  * <code>android:viewportWidth</code></dd>
+ * <dt><code>&lt;group></code></dt>
+ * <dd>Defines a group of paths or subgroups, plus transformation information.
+ * The transformations are defined in the same coordinates as the viewport.
+ * And the transformations are applied in the order of scale, rotate then translate. </dd>
+ * <dt><code>android:rotation</code>
+ * <dd>The degrees of rotation of the group.</dd></dt>
+ * <dt><code>android:pivotX</code>
+ * <dd>The X coordinate of the pivot for the scale and rotation of the group</dd></dt>
+ * <dt><code>android:pivotY</code>
+ * <dd>The Y coordinate of the pivot for the scale and rotation of the group</dd></dt>
+ * <dt><code>android:scaleX</code>
+ * <dd>The amount of scale on the X Coordinate</dd></dt>
+ * <dt><code>android:scaleY</code>
+ * <dd>The amount of scale on the Y coordinate</dd></dt>
+ * <dt><code>android:translateX</code>
+ * <dd>The amount of translation on the X coordinate</dd></dt>
+ * <dt><code>android:translateY</code>
+ * <dd>The amount of translation on the Y coordinate</dd></dt>
  * <dt><code>&lt;path></code></dt>
- * <dd>Defines paths to be drawn. Multiple paths can be defined in one xml file.
- * The paths are drawn in the order of their definition order.
+ * <dd>Defines paths to be drawn.
  * <dl>
  * <dt><code>android:name</code>
  * <dd>Defines the name of the path.</dd></dt>
@@ -76,12 +94,6 @@
  * <dd>The width a path stroke</dd></dt>
  * <dt><code>android:strokeOpacity</code>
  * <dd>The opacity of a path stroke</dd></dt>
- * <dt><code>android:rotation</code>
- * <dd>The amount to rotation the path stroke.</dd></dt>
- * <dt><code>android:pivotX</code>
- * <dd>The X coordinate of the center of rotation of a path</dd></dt>
- * <dt><code>android:pivotY</code>
- * <dd>The Y coordinate of the center of rotation of a path</dd></dt>
  * <dt><code>android:fillOpacity</code>
  * <dd>The opacity to fill the path with</dd></dt>
  * <dt><code>android:trimPathStart</code>
@@ -101,13 +113,13 @@
  * <dd>Sets the Miter limit for a stroked path</dd></dt>
  * </dl>
  * </dd>
- * @hide
  */
 public class VectorDrawable extends Drawable {
     private static final String LOGTAG = VectorDrawable.class.getSimpleName();
 
     private static final String SHAPE_SIZE = "size";
     private static final String SHAPE_VIEWPORT = "viewport";
+    private static final String SHAPE_GROUP = "group";
     private static final String SHAPE_PATH = "path";
     private static final String SHAPE_VECTOR = "vector";
 
@@ -119,9 +131,11 @@
     private static final int LINEJOIN_ROUND = 1;
     private static final int LINEJOIN_BEVEL = 2;
 
+    private static final boolean DBG_VECTOR_DRAWABLE = false;
+
     private final VectorDrawableState mVectorState;
 
-    private int mAlpha = 0xFF;
+    private final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<String, Object>();
 
     public VectorDrawable() {
         mVectorState = new VectorDrawableState(null);
@@ -135,6 +149,10 @@
         }
     }
 
+    Object getTargetByName(String name) {
+        return mVGTargetsMap.get(name);
+    }
+
     @Override
     public ConstantState getConstantState() {
         return mVectorState;
@@ -150,10 +168,14 @@
     }
 
     @Override
+    public int getAlpha() {
+        return mVectorState.mVPathRenderer.getRootAlpha();
+    }
+
+    @Override
     public void setAlpha(int alpha) {
-        // TODO correct handling of transparent
-        if (mAlpha != alpha) {
-            mAlpha = alpha;
+        if (mVectorState.mVPathRenderer.getRootAlpha() != alpha) {
+            mVectorState.mVPathRenderer.setRootAlpha(alpha);
             invalidateSelf();
         }
     }
@@ -260,24 +282,40 @@
         return null;
     }
 
+    private static int applyAlpha(int color, float alpha) {
+        int alphaBytes = Color.alpha(color);
+        color &= 0x00FFFFFF;
+        color |= ((int) (alphaBytes * alpha)) << 24;
+        return color;
+    }
+
     private VPathRenderer inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
             Theme theme) throws XmlPullParserException, IOException {
         final VPathRenderer pathRenderer = new VPathRenderer();
 
         boolean noSizeTag = true;
         boolean noViewportTag = true;
+        boolean noGroupTag = true;
         boolean noPathTag = true;
 
-        VGroup currentGroup = new VGroup();
+        // Use a stack to help to build the group tree.
+        // The top of the stack is always the current group.
+        final Stack<VGroup> groupStack = new Stack<VGroup>();
+        groupStack.push(pathRenderer.mRootGroup);
 
         int eventType = parser.getEventType();
         while (eventType != XmlPullParser.END_DOCUMENT) {
             if (eventType == XmlPullParser.START_TAG) {
                 final String tagName = parser.getName();
+                final VGroup currentGroup = groupStack.peek();
+
                 if (SHAPE_PATH.equals(tagName)) {
                     final VPath path = new VPath();
                     path.inflate(res, attrs, theme);
                     currentGroup.add(path);
+                    if (path.getPathName() != null) {
+                        mVGTargetsMap.put(path.getPathName(), path);
+                    }
                     noPathTag = false;
                 } else if (SHAPE_SIZE.equals(tagName)) {
                     pathRenderer.parseSize(res, attrs);
@@ -285,12 +323,30 @@
                 } else if (SHAPE_VIEWPORT.equals(tagName)) {
                     pathRenderer.parseViewport(res, attrs);
                     noViewportTag = false;
+                } else if (SHAPE_GROUP.equals(tagName)) {
+                    VGroup newChildGroup = new VGroup();
+                    newChildGroup.inflate(res, attrs, theme);
+                    currentGroup.mChildGroupList.add(newChildGroup);
+                    groupStack.push(newChildGroup);
+                    if (newChildGroup.getGroupName() != null) {
+                        mVGTargetsMap.put(newChildGroup.getGroupName(), newChildGroup);
+                    }
+                    noGroupTag = false;
+                }
+            } else if (eventType == XmlPullParser.END_TAG) {
+                final String tagName = parser.getName();
+                if (SHAPE_GROUP.equals(tagName)) {
+                    groupStack.pop();
                 }
             }
-
             eventType = parser.next();
         }
 
+        // Print the tree out for debug.
+        if (DBG_VECTOR_DRAWABLE) {
+            printGroupTree(pathRenderer.mRootGroup, 0);
+        }
+
         if (noSizeTag || noViewportTag || noPathTag) {
             final StringBuffer tag = new StringBuffer();
 
@@ -315,12 +371,24 @@
             throw new XmlPullParserException("no " + tag + " defined");
         }
 
-        pathRenderer.mCurrentGroup = currentGroup;
-        // post parse cleanup
-        pathRenderer.parseFinish();
         return pathRenderer;
     }
 
+    private void printGroupTree(VGroup currentGroup, int level) {
+        String indent = "";
+        for (int i = 0 ; i < level ; i++) {
+            indent += "    ";
+        }
+        // Print the current node
+        Log.v(LOGTAG, indent + "current group is :" +  currentGroup.getGroupName()
+                + " rotation is " + currentGroup.mRotate);
+        Log.v(LOGTAG, indent + "matrix is :" +  currentGroup.getLocalMatrix().toString());
+        // Then print all the children
+        for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) {
+            printGroupTree(currentGroup.mChildGroupList.get(i), level + 1);
+        }
+    }
+
     private void setPathRenderer(VPathRenderer pathRenderer) {
         mVectorState.mVPathRenderer = pathRenderer;
     }
@@ -333,6 +401,7 @@
         public VectorDrawableState(VectorDrawableState copy) {
             if (copy != null) {
                 mChangingConfigurations = copy.mChangingConfigurations;
+                // TODO: Make sure the constant state are handled correctly.
                 mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
                 mPadding = new Rect(copy.mPadding);
             }
@@ -360,35 +429,51 @@
     }
 
     private static class VPathRenderer {
+        /* Right now the internal data structure is organized as a tree.
+         * Each node can be a group node, or a path.
+         * A group node can have groups or paths as children, but a path node has
+         * no children.
+         * One example can be:
+         *                 Root Group
+         *                /    |     \
+         *           Group    Path    Group
+         *          /     \             |
+         *         Path   Path         Path
+         *
+         */
+        private final VGroup mRootGroup;
+
         private final Path mPath = new Path();
         private final Path mRenderPath = new Path();
-        private final Matrix mMatrix = new Matrix();
+        private static final Matrix IDENTITY_MATRIX = new Matrix();
 
-        private VPath[] mCurrentPaths;
         private Paint mStrokePaint;
         private Paint mFillPaint;
         private ColorFilter mColorFilter;
         private PathMeasure mPathMeasure;
 
-        private VGroup mCurrentGroup = new VGroup();
+        private float mBaseWidth = 0;
+        private float mBaseHeight = 0;
+        private float mViewportWidth = 0;
+        private float mViewportHeight = 0;
+        private int mRootAlpha = 0xFF;
 
-        float mBaseWidth = 0;
-        float mBaseHeight = 0;
-        float mViewportWidth = 0;
-        float mViewportHeight = 0;
+        private final Matrix mFinalPathMatrix = new Matrix();
 
         public VPathRenderer() {
+            mRootGroup = new VGroup();
+        }
+
+        public void setRootAlpha(int alpha) {
+            mRootAlpha = alpha;
+        }
+
+        public int getRootAlpha() {
+            return mRootAlpha;
         }
 
         public VPathRenderer(VPathRenderer copy) {
-            mCurrentGroup = copy.mCurrentGroup;
-            if (copy.mCurrentPaths != null) {
-                mCurrentPaths = new VPath[copy.mCurrentPaths.length];
-                for (int i = 0; i < mCurrentPaths.length; i++) {
-                    mCurrentPaths[i] = new VPath(copy.mCurrentPaths[i]);
-                }
-            }
-
+            mRootGroup = copy.mRootGroup;
             mBaseWidth = copy.mBaseWidth;
             mBaseHeight = copy.mBaseHeight;
             mViewportWidth = copy.mViewportHeight;
@@ -396,24 +481,59 @@
         }
 
         public boolean canApplyTheme() {
-            final ArrayList<VPath> paths = mCurrentGroup.mVGList;
+            // If one of the paths can apply theme, then return true;
+            return recursiveCanApplyTheme(mRootGroup);
+        }
+
+        private boolean recursiveCanApplyTheme(VGroup currentGroup) {
+            // We can do a tree traverse here, if there is one path return true,
+            // then we return true for the whole tree.
+            final ArrayList<VPath> paths = currentGroup.mPathList;
             for (int j = paths.size() - 1; j >= 0; j--) {
                 final VPath path = paths.get(j);
                 if (path.canApplyTheme()) {
                     return true;
                 }
             }
+
+            final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList;
+
+            for (int i = 0; i < childGroups.size(); i++) {
+                VGroup childGroup = childGroups.get(i);
+                if (childGroup.canApplyTheme()
+                        || recursiveCanApplyTheme(childGroup)) {
+                    return true;
+                }
+            }
             return false;
         }
 
         public void applyTheme(Theme t) {
-            final ArrayList<VPath> paths = mCurrentGroup.mVGList;
+            // Apply theme to every path of the tree.
+            recursiveApplyTheme(mRootGroup, t);
+        }
+
+        private void recursiveApplyTheme(VGroup currentGroup, Theme t) {
+            // We can do a tree traverse here, apply theme to all paths which
+            // can apply theme.
+            final ArrayList<VPath> paths = currentGroup.mPathList;
             for (int j = paths.size() - 1; j >= 0; j--) {
                 final VPath path = paths.get(j);
                 if (path.canApplyTheme()) {
                     path.applyTheme(t);
                 }
             }
+
+            final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList;
+
+            for (int i = 0; i < childGroups.size(); i++) {
+                VGroup childGroup = childGroups.get(i);
+                if (childGroup.canApplyTheme()) {
+                    childGroup.applyTheme(t);
+                }
+                recursiveApplyTheme(childGroup, t);
+            }
+
         }
 
         public void setColorFilter(ColorFilter colorFilter) {
@@ -426,107 +546,110 @@
             if (mStrokePaint != null) {
                 mStrokePaint.setColorFilter(colorFilter);
             }
+
+        }
+
+        private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix,
+                float currentAlpha, Canvas canvas, int w, int h) {
+            // Calculate current group's matrix by preConcat the parent's and
+            // and the current one on the top of the stack.
+            // Basically the Mfinal = Mviewport * M0 * M1 * M2;
+            // Mi the local matrix at level i of the group tree.
+            currentGroup.mStackedMatrix.set(currentMatrix);
+
+            currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
+
+            float stackedAlpha = currentAlpha * currentGroup.mGroupAlpha;
+            drawPath(currentGroup, stackedAlpha, canvas, w, h);
+            // Draw the group tree in post order.
+            for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) {
+                drawGroupTree(currentGroup.mChildGroupList.get(i),
+                        currentGroup.mStackedMatrix, stackedAlpha, canvas, w, h);
+            }
         }
 
         public void draw(Canvas canvas, int w, int h) {
-            if (mCurrentPaths == null) {
-                Log.e(LOGTAG,"mCurrentPaths == null");
-                return;
-            }
-
-            for (int i = 0; i < mCurrentPaths.length; i++) {
-                if (mCurrentPaths[i] != null) {
-                    drawPath(mCurrentPaths[i], canvas, w, h);
-                }
-            }
+            // Travese the tree in pre-order to draw.
+            drawGroupTree(mRootGroup, IDENTITY_MATRIX, ((float) mRootAlpha) / 0xFF, canvas, w, h);
         }
 
-        private void drawPath(VPath vPath, Canvas canvas, int w, int h) {
+        private void drawPath(VGroup vGroup, float stackedAlpha, Canvas canvas, int w, int h) {
             final float scale = Math.min(h / mViewportHeight, w / mViewportWidth);
 
-            vPath.toPath(mPath);
-            final Path path = mPath;
+            mFinalPathMatrix.set(vGroup.mStackedMatrix);
+            mFinalPathMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f);
+            mFinalPathMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f);
 
-            if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) {
-                float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f;
-                float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f;
+            ArrayList<VPath> paths = vGroup.getPaths();
+            for (int i = 0; i < paths.size(); i++) {
+                VPath vPath = paths.get(i);
+                vPath.toPath(mPath);
+                final Path path = mPath;
 
-                if (mPathMeasure == null) {
-                    mPathMeasure = new PathMeasure();
+                if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) {
+                    float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f;
+                    float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f;
+
+                    if (mPathMeasure == null) {
+                        mPathMeasure = new PathMeasure();
+                    }
+                    mPathMeasure.setPath(mPath, false);
+
+                    float len = mPathMeasure.getLength();
+                    start = start * len;
+                    end = end * len;
+                    path.reset();
+                    if (start > end) {
+                        mPathMeasure.getSegment(start, len, path, true);
+                        mPathMeasure.getSegment(0f, end, path, true);
+                    } else {
+                        mPathMeasure.getSegment(start, end, path, true);
+                    }
+                    path.rLineTo(0, 0); // fix bug in measure
                 }
-                mPathMeasure.setPath(mPath, false);
 
-                float len = mPathMeasure.getLength();
-                start = start * len;
-                end = end * len;
-                path.reset();
-                if (start > end) {
-                    mPathMeasure.getSegment(start, len, path, true);
-                    mPathMeasure.getSegment(0f, end, path, true);
+                mRenderPath.reset();
+
+                mRenderPath.addPath(path, mFinalPathMatrix);
+
+                if (vPath.mClip) {
+                    canvas.clipPath(mRenderPath, Region.Op.REPLACE);
                 } else {
-                    mPathMeasure.getSegment(start, end, path, true);
+                   if (vPath.mFillColor != 0) {
+                        if (mFillPaint == null) {
+                            mFillPaint = new Paint();
+                            mFillPaint.setColorFilter(mColorFilter);
+                            mFillPaint.setStyle(Paint.Style.FILL);
+                            mFillPaint.setAntiAlias(true);
+                        }
+                        mFillPaint.setColor(applyAlpha(vPath.mFillColor, stackedAlpha));
+                        canvas.drawPath(mRenderPath, mFillPaint);
+                    }
+
+                    if (vPath.mStrokeColor != 0) {
+                        if (mStrokePaint == null) {
+                            mStrokePaint = new Paint();
+                            mStrokePaint.setColorFilter(mColorFilter);
+                            mStrokePaint.setStyle(Paint.Style.STROKE);
+                            mStrokePaint.setAntiAlias(true);
+                        }
+
+                        final Paint strokePaint = mStrokePaint;
+                        if (vPath.mStrokeLineJoin != null) {
+                            strokePaint.setStrokeJoin(vPath.mStrokeLineJoin);
+                        }
+
+                        if (vPath.mStrokeLineCap != null) {
+                            strokePaint.setStrokeCap(vPath.mStrokeLineCap);
+                        }
+
+                        strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale);
+
+                        strokePaint.setColor(applyAlpha(vPath.mStrokeColor, stackedAlpha));
+                        strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale);
+                        canvas.drawPath(mRenderPath, strokePaint);
+                    }
                 }
-                path.rLineTo(0, 0); // fix bug in measure
-            }
-
-            mRenderPath.reset();
-            mMatrix.reset();
-
-            mMatrix.postRotate(vPath.mRotate, vPath.mPivotX, vPath.mPivotY);
-            mMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f);
-            mMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f);
-
-            mRenderPath.addPath(path, mMatrix);
-
-            if (vPath.mClip) {
-                canvas.clipPath(mRenderPath, Region.Op.REPLACE);
-            }
-
-            if (vPath.mFillColor != 0) {
-                if (mFillPaint == null) {
-                    mFillPaint = new Paint();
-                    mFillPaint.setColorFilter(mColorFilter);
-                    mFillPaint.setStyle(Paint.Style.FILL);
-                    mFillPaint.setAntiAlias(true);
-                }
-
-                mFillPaint.setColor(vPath.mFillColor);
-                canvas.drawPath(mRenderPath, mFillPaint);
-            }
-
-            if (vPath.mStrokeColor != 0) {
-                if (mStrokePaint == null) {
-                    mStrokePaint = new Paint();
-                    mStrokePaint.setColorFilter(mColorFilter);
-                    mStrokePaint.setStyle(Paint.Style.STROKE);
-                    mStrokePaint.setAntiAlias(true);
-                }
-
-                final Paint strokePaint = mStrokePaint;
-                if (vPath.mStrokeLineJoin != null) {
-                    strokePaint.setStrokeJoin(vPath.mStrokeLineJoin);
-                }
-
-                if (vPath.mStrokeLineCap != null) {
-                    strokePaint.setStrokeCap(vPath.mStrokeLineCap);
-                }
-
-                strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale);
-                strokePaint.setColor(vPath.mStrokeColor);
-                strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale);
-                canvas.drawPath(mRenderPath, strokePaint);
-            }
-        }
-
-        /**
-         * Build the "current" path based on the current group
-         * TODO: improve memory use & performance or move to C++
-         */
-        public void parseFinish() {
-            final Collection<VPath> paths = mCurrentGroup.getPaths();
-            mCurrentPaths = paths.toArray(new VPath[paths.size()]);
-            for (int i = 0; i < mCurrentPaths.length; i++) {
-                mCurrentPaths[i] = new VPath(mCurrentPaths[i]);
             }
         }
 
@@ -566,43 +689,233 @@
 
     }
 
-    private static class VGroup {
-        private final HashMap<String, VPath> mVGPathMap = new HashMap<String, VPath>();
-        private final ArrayList<VPath> mVGList = new ArrayList<VPath>();
+    static class VGroup {
+        private final ArrayList<VPath> mPathList = new ArrayList<VPath>();
+        private final ArrayList<VGroup> mChildGroupList = new ArrayList<VGroup>();
+
+        private float mRotate = 0;
+        private float mPivotX = 0;
+        private float mPivotY = 0;
+        private float mScaleX = 1;
+        private float mScaleY = 1;
+        private float mTranslateX = 0;
+        private float mTranslateY = 0;
+        private float mGroupAlpha = 1;
+
+        // mLocalMatrix is parsed from the XML.
+        private final Matrix mLocalMatrix = new Matrix();
+        // mStackedMatrix is only used when drawing, it combines all the
+        // parents' local matrices with the current one.
+        private final Matrix mStackedMatrix = new Matrix();
+
+        private int[] mThemeAttrs;
+
+        private String mGroupName = null;
+
+        /* Getter and Setter */
+        public float getRotation() {
+            return mRotate;
+        }
+
+        public void setRotation(float rotation) {
+            if (rotation != mRotate) {
+                mRotate = rotation;
+                updateLocalMatrix();
+            }
+        }
+
+        public float getPivotX() {
+            return mPivotX;
+        }
+
+        public void setPivotX(float pivotX) {
+            if (pivotX != mPivotX) {
+                mPivotX = pivotX;
+                updateLocalMatrix();
+            }
+        }
+
+        public float getPivotY() {
+            return mPivotY;
+        }
+
+        public void setPivotY(float pivotY) {
+            if (pivotY != mPivotY) {
+                mPivotY = pivotY;
+                updateLocalMatrix();
+            }
+        }
+
+        public float getScaleX() {
+            return mScaleX;
+        }
+
+        public void setScaleX(float scaleX) {
+            if (scaleX != mScaleX) {
+                mScaleX = scaleX;
+                updateLocalMatrix();
+            }
+        }
+
+        public float getScaleY() {
+            return mScaleY;
+        }
+
+        public void setScaleY(float scaleY) {
+            if (scaleY != mScaleY) {
+                mScaleY = scaleY;
+                updateLocalMatrix();
+            }
+        }
+
+        public float getTranslateX() {
+            return mTranslateX;
+        }
+
+        public void setTranslateX(float translateX) {
+            if (translateX != mTranslateX) {
+                mTranslateX = translateX;
+                updateLocalMatrix();
+            }
+        }
+
+        public float getTranslateY() {
+            return mTranslateY;
+        }
+
+        public void setTranslateY(float translateY) {
+            if (translateY != mTranslateY) {
+                mTranslateY = translateY;
+                updateLocalMatrix();
+            }
+        }
+
+        public float getAlpha() {
+            return mGroupAlpha;
+        }
+
+        public void setAlpha(float groupAlpha) {
+            if (groupAlpha != mGroupAlpha) {
+                mGroupAlpha = groupAlpha;
+            }
+        }
+
+        public String getGroupName() {
+            return mGroupName;
+        }
+
+        public Matrix getLocalMatrix() {
+            return mLocalMatrix;
+        }
 
         public void add(VPath path) {
-            String id = path.getID();
-            mVGPathMap.put(id, path);
-            mVGList.add(path);
+            mPathList.add(path);
          }
 
+        public boolean canApplyTheme() {
+            return mThemeAttrs != null;
+        }
+
+        public void applyTheme(Theme t) {
+            if (mThemeAttrs == null) {
+                return;
+            }
+
+            final TypedArray a = t.resolveAttributes(
+                    mThemeAttrs, R.styleable.VectorDrawablePath);
+
+            mRotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, mRotate);
+            mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mPivotX);
+            mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, mPivotY);
+            mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, mScaleX);
+            mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY);
+            mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX);
+            mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY);
+            mGroupAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mGroupAlpha);
+            updateLocalMatrix();
+            if (a.hasValue(R.styleable.VectorDrawableGroup_name)) {
+                mGroupName = a.getString(R.styleable.VectorDrawableGroup_name);
+            }
+            a.recycle();
+        }
+
+        public void inflate(Resources res, AttributeSet attrs, Theme theme) {
+            final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawableGroup);
+            final int[] themeAttrs = a.extractThemeAttrs();
+
+            mThemeAttrs = themeAttrs;
+            // NOTE: The set of attributes loaded here MUST match the
+            // set of attributes loaded in applyTheme.
+
+            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_rotation] == 0) {
+                mRotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, mRotate);
+            }
+
+            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_pivotX] == 0) {
+                mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mPivotX);
+            }
+
+            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_pivotY] == 0) {
+                mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, mPivotY);
+            }
+
+            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_scaleX] == 0) {
+                mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, mScaleX);
+            }
+
+            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_scaleY] == 0) {
+                mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY);
+            }
+
+            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_translateX] == 0) {
+                mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX);
+            }
+
+            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_translateY] == 0) {
+                mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY);
+            }
+
+            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_name] == 0) {
+                mGroupName = a.getString(R.styleable.VectorDrawableGroup_name);
+            }
+
+            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_alpha] == 0) {
+                mGroupAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mGroupAlpha);
+            }
+
+            updateLocalMatrix();
+            a.recycle();
+        }
+
+        private void updateLocalMatrix() {
+            // The order we apply is the same as the
+            // RenderNode.cpp::applyViewPropertyTransforms().
+            mLocalMatrix.reset();
+            mLocalMatrix.postTranslate(-mPivotX, -mPivotY);
+            mLocalMatrix.postScale(mScaleX, mScaleY);
+            mLocalMatrix.postRotate(mRotate, 0, 0);
+            mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
+        }
+
         /**
          * Must return in order of adding
          * @return ordered list of paths
          */
-        public Collection<VPath> getPaths() {
-            return mVGList;
+        public ArrayList<VPath> getPaths() {
+            return mPathList;
         }
 
     }
 
-    private static class VPath {
-        private static final int MAX_STATES = 10;
-
+    static class VPath {
         private int[] mThemeAttrs;
 
         int mStrokeColor = 0;
         float mStrokeWidth = 0;
         float mStrokeOpacity = Float.NaN;
-
-        int mFillColor = 0;
+        int mFillColor = Color.BLACK;
         int mFillRule;
         float mFillOpacity = Float.NaN;
-
-        float mRotate = 0;
-        float mPivotX = 0;
-        float mPivotY = 0;
-
         float mTrimPathStart = 0;
         float mTrimPathEnd = 1;
         float mTrimPathOffset = 0;
@@ -613,19 +926,12 @@
         float mStrokeMiterlimit = 4;
 
         private VNode[] mNode = null;
-        private String mId;
-        private int[] mCheckState = new int[MAX_STATES];
-        private boolean[] mCheckValue = new boolean[MAX_STATES];
-        private int mNumberOfStates = 0;
+        private String mPathName;
 
         public VPath() {
             // Empty constructor.
         }
 
-        public VPath(VPath p) {
-            copyFrom(p);
-        }
-
         public void toPath(Path path) {
             path.reset();
             if (mNode != null) {
@@ -633,8 +939,8 @@
             }
         }
 
-        public String getID() {
-            return mId;
+        public String getPathName() {
+            return mPathName;
         }
 
         private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) {
@@ -663,6 +969,71 @@
             }
         }
 
+        /* Setters and Getters */
+        int getStroke() {
+            return mStrokeColor;
+        }
+
+        void setStroke(int strokeColor) {
+            mStrokeColor = strokeColor;
+        }
+
+        float getStrokeWidth() {
+            return mStrokeWidth;
+        }
+
+        void setStrokeWidth(float strokeWidth) {
+            mStrokeWidth = strokeWidth;
+        }
+
+        float getStrokeOpacity() {
+            return mStrokeOpacity;
+        }
+
+        void setStrokeOpacity(float strokeOpacity) {
+            mStrokeOpacity = strokeOpacity;
+        }
+
+        int getFill() {
+            return mFillColor;
+        }
+
+        void setFill(int fillColor) {
+            mFillColor = fillColor;
+        }
+
+        float getFillOpacity() {
+            return mFillOpacity;
+        }
+
+        void setFillOpacity(float fillOpacity) {
+            mFillOpacity = fillOpacity;
+        }
+
+        float getTrimPathStart() {
+            return mTrimPathStart;
+        }
+
+        void setTrimPathStart(float trimPathStart) {
+            mTrimPathStart = trimPathStart;
+        }
+
+        float getTrimPathEnd() {
+            return mTrimPathEnd;
+        }
+
+        void setTrimPathEnd(float trimPathEnd) {
+            mTrimPathEnd = trimPathEnd;
+        }
+
+        float getTrimPathOffset() {
+            return mTrimPathOffset;
+        }
+
+        void setTrimPathOffset(float trimPathOffset) {
+            mTrimPathOffset = trimPathOffset;
+        }
+
         public void inflate(Resources r, AttributeSet attrs, Theme theme) {
             final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawablePath);
             final int[] themeAttrs = a.extractThemeAttrs();
@@ -675,7 +1046,7 @@
             }
 
             if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_name] == 0) {
-                mId = a.getString(R.styleable.VectorDrawablePath_name);
+                mPathName = a.getString(R.styleable.VectorDrawablePath_name);
             }
 
             if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pathData] == 0) {
@@ -690,18 +1061,6 @@
                 mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity);
             }
 
-            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_rotation] == 0) {
-                mRotate = a.getFloat(R.styleable.VectorDrawablePath_rotation, mRotate);
-            }
-
-            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pivotX] == 0) {
-                mPivotX = a.getFloat(R.styleable.VectorDrawablePath_pivotX, mPivotX);
-            }
-
-            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pivotY] == 0) {
-                mPivotY = a.getFloat(R.styleable.VectorDrawablePath_pivotY, mPivotY);
-            }
-
             if (themeAttrs == null
                     || themeAttrs[R.styleable.VectorDrawablePath_strokeLineCap] == 0) {
                 mStrokeLineCap = getStrokeLineCap(
@@ -770,7 +1129,7 @@
             mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip);
 
             if (a.hasValue(R.styleable.VectorDrawablePath_name)) {
-                mId = a.getString(R.styleable.VectorDrawablePath_name);
+                mPathName = a.getString(R.styleable.VectorDrawablePath_name);
             }
 
             if (a.hasValue(R.styleable.VectorDrawablePath_pathData)) {
@@ -780,10 +1139,6 @@
             mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor);
             mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity);
 
-            mRotate = a.getFloat(R.styleable.VectorDrawablePath_rotation, mRotate);
-            mPivotX = a.getFloat(R.styleable.VectorDrawablePath_pivotX, mPivotX);
-            mPivotY = a.getFloat(R.styleable.VectorDrawablePath_pivotY, mPivotY);
-
             mStrokeLineCap = getStrokeLineCap(a.getInt(
                     R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap);
             mStrokeLineJoin = getStrokeLineJoin(a.getInt(
@@ -802,17 +1157,16 @@
                     R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart);
 
             updateColorAlphas();
+            a.recycle();
         }
 
         private void updateColorAlphas() {
             if (!Float.isNaN(mFillOpacity)) {
-                mFillColor &= 0x00FFFFFF;
-                mFillColor |= ((int) (0xFF * mFillOpacity)) << 24;
+                mFillColor = applyAlpha(mFillColor, mFillOpacity);
             }
 
             if (!Float.isNaN(mStrokeOpacity)) {
-                mStrokeColor &= 0x00FFFFFF;
-                mStrokeColor |= ((int) (0xFF * mStrokeOpacity)) << 24;
+                mStrokeColor = applyAlpha(mStrokeColor, mStrokeOpacity);
             }
         }
 
@@ -904,33 +1258,6 @@
             }
             return list.toArray(new VectorDrawable.VNode[list.size()]);
         }
-
-        public void copyFrom(VPath p1) {
-            mNode = new VNode[p1.mNode.length];
-            for (int i = 0; i < mNode.length; i++) {
-                mNode[i] = new VNode(p1.mNode[i]);
-            }
-            mId = p1.mId;
-            mStrokeColor = p1.mStrokeColor;
-            mFillColor = p1.mFillColor;
-            mStrokeWidth = p1.mStrokeWidth;
-            mRotate = p1.mRotate;
-            mPivotX = p1.mPivotX;
-            mPivotY = p1.mPivotY;
-            mTrimPathStart = p1.mTrimPathStart;
-            mTrimPathEnd = p1.mTrimPathEnd;
-            mTrimPathOffset = p1.mTrimPathOffset;
-            mStrokeLineCap = p1.mStrokeLineCap;
-            mStrokeLineJoin = p1.mStrokeLineJoin;
-            mStrokeMiterlimit = p1.mStrokeMiterlimit;
-            mNumberOfStates = p1.mNumberOfStates;
-            for (int i = 0; i < mNumberOfStates; i++) {
-                mCheckState[i] = p1.mCheckState[i];
-                mCheckValue[i] = p1.mCheckValue[i];
-            }
-
-            mFillRule = p1.mFillRule;
-        }
     }
 
     private static class VNode {
diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java
index 39795b5..b63edce 100644
--- a/graphics/java/android/graphics/pdf/PdfRenderer.java
+++ b/graphics/java/android/graphics/pdf/PdfRenderer.java
@@ -188,6 +188,7 @@
     private void doClose() {
         if (mCurrentPage != null) {
             mCurrentPage.close();
+            mCurrentPage = null;
         }
         nativeClose(mNativeDocument);
         try {
@@ -374,7 +375,6 @@
             nativeClosePage(mNativePage);
             mNativePage = 0;
             mCloseGuard.close();
-            mCurrentPage = null;
         }
 
         private void throwIfClosed() {
diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java
index 21d6caa..4a823cc 100644
--- a/keystore/java/android/security/KeyPairGeneratorSpec.java
+++ b/keystore/java/android/security/KeyPairGeneratorSpec.java
@@ -35,7 +35,7 @@
 /**
  * This provides the required parameters needed for initializing the
  * {@code KeyPairGenerator} that works with
- * <a href="{@docRoot}guide/topics/security/keystore.html">Android KeyStore
+ * <a href="{@docRoot}training/articles/keystore.html">Android KeyStore
  * facility</a>. The Android KeyStore facility is accessed through a
  * {@link java.security.KeyPairGenerator} API using the {@code AndroidKeyStore}
  * provider. The {@code context} passed in may be used to pop up some UI to ask
@@ -306,7 +306,7 @@
      * Builder class for {@link KeyPairGeneratorSpec} objects.
      * <p>
      * This will build a parameter spec for use with the <a href="{@docRoot}
-     * guide/topics/security/keystore.html">Android KeyStore facility</a>.
+     * training/articles/keystore.html">Android KeyStore facility</a>.
      * <p>
      * The required fields must be filled in with the builder.
      * <p>
diff --git a/keystore/java/android/security/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java
index b71efc4..2eeb6ad 100644
--- a/keystore/java/android/security/KeyStoreParameter.java
+++ b/keystore/java/android/security/KeyStoreParameter.java
@@ -24,7 +24,7 @@
 /**
  * This provides the optional parameters that can be specified for
  * {@code KeyStore} entries that work with
- * <a href="{@docRoot}guide/topics/security/keystore.html">Android KeyStore
+ * <a href="{@docRoot}training/articles/keystore.html">Android KeyStore
  * facility</a>. The Android KeyStore facility is accessed through a
  * {@link java.security.KeyStore} API using the {@code AndroidKeyStore}
  * provider. The {@code context} passed in may be used to pop up some UI to ask
@@ -67,7 +67,7 @@
      * Builder class for {@link KeyStoreParameter} objects.
      * <p>
      * This will build protection parameters for use with the
-     * <a href="{@docRoot}guide/topics/security/keystore.html">Android KeyStore
+     * <a href="{@docRoot}training/articles/keystore.html">Android KeyStore
      * facility</a>.
      * <p>
      * This can be used to require that KeyStore entries be stored encrypted.
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 900a72e..02e85fe 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -14,6 +14,7 @@
 		AmbientShadow.cpp \
 		Animator.cpp \
 		AssetAtlas.cpp \
+		DamageAccumulator.cpp \
 		FontRenderer.cpp \
 		GammaFontRenderer.cpp \
 		Caches.cpp \
@@ -72,8 +73,6 @@
 		$(LOCAL_PATH)/../../include/utils \
 		external/skia/src/core
 
-	include external/stlport/libstlport.mk
-
 	LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES
 	LOCAL_CFLAGS += -Wno-unused-parameter
 	LOCAL_MODULE_CLASS := SHARED_LIBRARIES
@@ -81,16 +80,15 @@
 	LOCAL_MODULE := libhwui
 	LOCAL_MODULE_TAGS := optional
 
+	include external/stlport/libstlport.mk
+
 	ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT))
 		LOCAL_CFLAGS += -DANDROID_ENABLE_RENDERSCRIPT
-		LOCAL_SHARED_LIBRARIES += libRS libRScpp libstlport
+		LOCAL_SHARED_LIBRARIES += libRS libRScpp
 		LOCAL_C_INCLUDES += \
 			$(intermediates) \
 			frameworks/rs/cpp \
-			frameworks/rs \
-			external/stlport/stlport \
-			bionic/ \
-			bionic/libstdc++/include
+			frameworks/rs
 	endif
 
 	ifndef HWUI_COMPILE_SYMBOLS
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index a0c7c55..203cdff 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -117,7 +117,7 @@
     virtual void setValue(RenderNode* target, float value);
 
 private:
-    typedef void (RenderProperties::*SetFloatProperty)(float value);
+    typedef bool (RenderProperties::*SetFloatProperty)(float value);
     typedef float (RenderProperties::*GetFloatProperty)() const;
 
     struct PropertyAccessors;
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
new file mode 100644
index 0000000..898e81a
--- /dev/null
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "DamageAccumulator"
+
+#include "DamageAccumulator.h"
+
+#include <cutils/log.h>
+
+#include "RenderNode.h"
+#include "utils/MathUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+NullDamageAccumulator NullDamageAccumulator::sInstance;
+
+NullDamageAccumulator* NullDamageAccumulator::instance() {
+    return &sInstance;
+}
+
+enum TransformType {
+    TransformRenderNode,
+    TransformMatrix4,
+};
+
+struct DirtyStack {
+    TransformType type;
+    union {
+        const RenderNode* renderNode;
+        const Matrix4* matrix4;
+    };
+    // When this frame is pop'd, this rect is mapped through the above transform
+    // and applied to the previous (aka parent) frame
+    SkRect pendingDirty;
+    DirtyStack* prev;
+    DirtyStack* next;
+};
+
+DamageAccumulator::DamageAccumulator() {
+    mHead = (DirtyStack*) mAllocator.alloc(sizeof(DirtyStack));
+    memset(mHead, 0, sizeof(DirtyStack));
+    // Create a root that we will not pop off
+    mHead->prev = mHead;
+}
+
+void DamageAccumulator::pushCommon() {
+    if (!mHead->next) {
+        DirtyStack* nextFrame = (DirtyStack*) mAllocator.alloc(sizeof(DirtyStack));
+        nextFrame->next = 0;
+        nextFrame->prev = mHead;
+        mHead->next = nextFrame;
+    }
+    mHead = mHead->next;
+    mHead->pendingDirty.setEmpty();
+}
+
+void DamageAccumulator::pushTransform(const RenderNode* transform) {
+    pushCommon();
+    mHead->type = TransformRenderNode;
+    mHead->renderNode = transform;
+}
+
+void DamageAccumulator::pushTransform(const Matrix4* transform) {
+    pushCommon();
+    mHead->type = TransformMatrix4;
+    mHead->matrix4 = transform;
+}
+
+void DamageAccumulator::popTransform() {
+    LOG_ALWAYS_FATAL_IF(mHead->prev == mHead, "Cannot pop the root frame!");
+    DirtyStack* dirtyFrame = mHead;
+    mHead = mHead->prev;
+    if (dirtyFrame->type == TransformRenderNode) {
+        applyRenderNodeTransform(dirtyFrame);
+    } else {
+        applyMatrix4Transform(dirtyFrame);
+    }
+}
+
+static inline void mapRect(const Matrix4* matrix, const SkRect& in, SkRect* out) {
+    if (in.isEmpty()) return;
+    Rect temp(in);
+    matrix->mapRect(temp);
+    out->join(RECT_ARGS(temp));
+}
+
+void DamageAccumulator::applyMatrix4Transform(DirtyStack* frame) {
+    mapRect(frame->matrix4, frame->pendingDirty, &mHead->pendingDirty);
+}
+
+static inline void mapRect(const RenderProperties& props, const SkRect& in, SkRect* out) {
+    if (in.isEmpty()) return;
+    const SkMatrix* transform = props.getTransformMatrix();
+    SkRect temp(in);
+    if (transform && !transform->isIdentity()) {
+        transform->mapRect(&temp);
+    }
+    temp.offset(props.getLeft(), props.getTop());
+    out->join(temp);
+}
+
+static DirtyStack* findParentRenderNode(DirtyStack* frame) {
+    while (frame->prev != frame) {
+        frame = frame->prev;
+        if (frame->type == TransformRenderNode) {
+            return frame;
+        }
+    }
+    return NULL;
+}
+
+static DirtyStack* findProjectionReceiver(DirtyStack* frame) {
+    if (frame) {
+        while (frame->prev != frame) {
+            frame = frame->prev;
+            if (frame->type == TransformRenderNode
+                    && frame->renderNode->hasProjectionReceiver()) {
+                return frame;
+            }
+        }
+    }
+    return NULL;
+}
+
+static void applyTransforms(DirtyStack* frame, DirtyStack* end) {
+    SkRect* rect = &frame->pendingDirty;
+    while (frame != end) {
+        if (frame->type == TransformRenderNode) {
+            mapRect(frame->renderNode->properties(), *rect, rect);
+        } else {
+            mapRect(frame->matrix4, *rect, rect);
+        }
+        frame = frame->prev;
+    }
+}
+
+void DamageAccumulator::applyRenderNodeTransform(DirtyStack* frame) {
+    if (frame->pendingDirty.isEmpty()) {
+        return;
+    }
+
+    const RenderProperties& props = frame->renderNode->properties();
+
+    // Perform clipping
+    if (props.getClipToBounds() && !frame->pendingDirty.isEmpty()) {
+        if (!frame->pendingDirty.intersect(0, 0, props.getWidth(), props.getHeight())) {
+            frame->pendingDirty.setEmpty();
+        }
+    }
+
+    // apply all transforms
+    mapRect(props, frame->pendingDirty, &mHead->pendingDirty);
+
+    // project backwards if necessary
+    if (props.getProjectBackwards() && !frame->pendingDirty.isEmpty()) {
+        // First, find our parent RenderNode:
+        DirtyStack* parentNode = findParentRenderNode(frame);
+        // Find our parent's projection receiver, which is what we project onto
+        DirtyStack* projectionReceiver = findProjectionReceiver(parentNode);
+        if (projectionReceiver) {
+            applyTransforms(frame, projectionReceiver);
+            projectionReceiver->pendingDirty.join(frame->pendingDirty);
+        } else {
+            ALOGW("Failed to find projection receiver? Dropping on the floor...");
+        }
+
+        frame->pendingDirty.setEmpty();
+    }
+}
+
+void DamageAccumulator::dirty(float left, float top, float right, float bottom) {
+    mHead->pendingDirty.join(left, top, right, bottom);
+}
+
+void DamageAccumulator::finish(SkRect* totalDirty) {
+    LOG_ALWAYS_FATAL_IF(mHead->prev != mHead, "Cannot finish, mismatched push/pop calls! %p vs. %p", mHead->prev, mHead);
+    // Root node never has a transform, so this is the fully mapped dirty rect
+    *totalDirty = mHead->pendingDirty;
+    totalDirty->roundOut();
+    mHead->pendingDirty.setEmpty();
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h
new file mode 100644
index 0000000..2ca30d4
--- /dev/null
+++ b/libs/hwui/DamageAccumulator.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef DAMAGEACCUMULATOR_H
+#define DAMAGEACCUMULATOR_H
+
+#include <cutils/compiler.h>
+#include <utils/LinearAllocator.h>
+
+#include <SkMatrix.h>
+#include <SkRect.h>
+
+#include "utils/Macros.h"
+
+namespace android {
+namespace uirenderer {
+
+struct DirtyStack;
+class RenderNode;
+class Matrix4;
+
+class IDamageAccumulator {
+public:
+    virtual void pushTransform(const RenderNode* transform) = 0;
+    virtual void pushTransform(const Matrix4* transform) = 0;
+    virtual void popTransform() = 0;
+    virtual void dirty(float left, float top, float right, float bottom) = 0;
+protected:
+    virtual ~IDamageAccumulator() {}
+};
+
+class DamageAccumulator : public IDamageAccumulator {
+    PREVENT_COPY_AND_ASSIGN(DamageAccumulator);
+public:
+    DamageAccumulator();
+    // mAllocator will clean everything up for us, no need for a dtor
+
+    // Push a transform node onto the stack. This should be called prior
+    // to any dirty() calls. Subsequent calls to dirty()
+    // will be affected by the transform when popTransform() is called.
+    virtual void pushTransform(const RenderNode* transform);
+    virtual void pushTransform(const Matrix4* transform);
+
+    // Pops a transform node from the stack, propagating the dirty rect
+    // up to the parent node. Returns the IDamageTransform that was just applied
+    virtual void popTransform();
+
+    virtual void dirty(float left, float top, float right, float bottom);
+
+    void finish(SkRect* totalDirty);
+
+private:
+    void pushCommon();
+    void applyMatrix4Transform(DirtyStack* frame);
+    void applyRenderNodeTransform(DirtyStack* frame);
+
+    LinearAllocator mAllocator;
+    DirtyStack* mHead;
+};
+
+class NullDamageAccumulator : public IDamageAccumulator {
+    PREVENT_COPY_AND_ASSIGN(NullDamageAccumulator);
+public:
+    virtual void pushTransform(const RenderNode* transform) { }
+    virtual void pushTransform(const Matrix4* transform) { }
+    virtual void popTransform() { }
+    virtual void dirty(float left, float top, float right, float bottom) { }
+
+    ANDROID_API static NullDamageAccumulator* instance();
+
+private:
+    NullDamageAccumulator() {}
+    ~NullDamageAccumulator() {}
+
+    static NullDamageAccumulator sInstance;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* DAMAGEACCUMULATOR_H */
diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp
index 97e9bf6..d494c4c 100644
--- a/libs/hwui/DeferredLayerUpdater.cpp
+++ b/libs/hwui/DeferredLayerUpdater.cpp
@@ -81,6 +81,10 @@
             success = LayerRenderer::resizeLayer(mLayer, mWidth, mHeight);
         }
         mLayer->setBlend(mBlend);
+        // TODO: Use DamageAccumulator to get the damage area for the layer's
+        // subtree to only update that part of the layer. Do this as part of
+        // reworking layers to be a RenderProperty instead of a View-managed object
+        mDirtyRect.set(0, 0, mWidth, mHeight);
         mDisplayList->prepareTree(info);
         mLayer->updateDeferred(mDisplayList.get(),
                 mDirtyRect.left, mDirtyRect.top, mDirtyRect.right, mDirtyRect.bottom);
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index 96c6292..f418c9b 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -80,10 +80,6 @@
         delete paths.itemAt(i);
     }
 
-    for (size_t i = 0; i < matrices.size(); i++) {
-        delete matrices.itemAt(i);
-    }
-
     bitmapResources.clear();
     ownedBitmapResources.clear();
     patchResources.clear();
@@ -91,7 +87,6 @@
     paints.clear();
     regions.clear();
     paths.clear();
-    matrices.clear();
     layers.clear();
 }
 
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 11e78b0..7b7dc16 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -125,7 +125,6 @@
     Vector<const SkPath*> paths;
     SortedVector<const SkPath*> sourcePaths;
     Vector<const SkRegion*> regions;
-    Vector<const SkMatrix*> matrices;
     Vector<Layer*> layers;
     uint32_t functorCount;
     bool hasDrawOps;
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 3281116..233f3f0 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -472,7 +472,7 @@
 
 class SetMatrixOp : public StateOp {
 public:
-    SetMatrixOp(const SkMatrix* matrix)
+    SetMatrixOp(const SkMatrix& matrix)
             : mMatrix(matrix) {}
 
     virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
@@ -480,22 +480,22 @@
     }
 
     virtual void output(int level, uint32_t logFlags) const {
-        if (mMatrix) {
-            OP_LOG("SetMatrix " SK_MATRIX_STRING, SK_MATRIX_ARGS(mMatrix));
-        } else {
+        if (mMatrix.isIdentity()) {
             OP_LOGS("SetMatrix (reset)");
+        } else {
+            OP_LOG("SetMatrix " SK_MATRIX_STRING, SK_MATRIX_ARGS(&mMatrix));
         }
     }
 
     virtual const char* name() { return "SetMatrix"; }
 
 private:
-    const SkMatrix* mMatrix;
+    const SkMatrix mMatrix;
 };
 
 class ConcatMatrixOp : public StateOp {
 public:
-    ConcatMatrixOp(const SkMatrix* matrix)
+    ConcatMatrixOp(const SkMatrix& matrix)
             : mMatrix(matrix) {}
 
     virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
@@ -503,13 +503,13 @@
     }
 
     virtual void output(int level, uint32_t logFlags) const {
-        OP_LOG("ConcatMatrix " SK_MATRIX_STRING, SK_MATRIX_ARGS(mMatrix));
+        OP_LOG("ConcatMatrix " SK_MATRIX_STRING, SK_MATRIX_ARGS(&mMatrix));
     }
 
     virtual const char* name() { return "ConcatMatrix"; }
 
 private:
-    const SkMatrix* mMatrix;
+    const SkMatrix mMatrix;
 };
 
 class ClipOp : public StateOp {
@@ -746,10 +746,10 @@
 
 class DrawBitmapMatrixOp : public DrawBoundedOp {
 public:
-    DrawBitmapMatrixOp(const SkBitmap* bitmap, const SkMatrix* matrix, const SkPaint* paint)
+    DrawBitmapMatrixOp(const SkBitmap* bitmap, const SkMatrix& matrix, const SkPaint* paint)
             : DrawBoundedOp(paint), mBitmap(bitmap), mMatrix(matrix) {
         mLocalBounds.set(0, 0, bitmap->width(), bitmap->height());
-        const mat4 transform(*matrix);
+        const mat4 transform(matrix);
         transform.mapRect(mLocalBounds);
     }
 
@@ -758,7 +758,7 @@
     }
 
     virtual void output(int level, uint32_t logFlags) const {
-        OP_LOG("Draw bitmap %p matrix " SK_MATRIX_STRING, mBitmap, SK_MATRIX_ARGS(mMatrix));
+        OP_LOG("Draw bitmap %p matrix " SK_MATRIX_STRING, mBitmap, SK_MATRIX_ARGS(&mMatrix));
     }
 
     virtual const char* name() { return "DrawBitmapMatrix"; }
@@ -770,7 +770,7 @@
 
 private:
     const SkBitmap* mBitmap;
-    const SkMatrix* mMatrix;
+    const SkMatrix mMatrix;
 };
 
 class DrawBitmapRectOp : public DrawBoundedOp {
@@ -788,7 +788,7 @@
     }
 
     virtual void output(int level, uint32_t logFlags) const {
-        OP_LOG("Draw bitmap %p src="RECT_STRING", dst="RECT_STRING,
+        OP_LOG("Draw bitmap %p src=" RECT_STRING ", dst=" RECT_STRING,
                 mBitmap, RECT_ARGS(mSrc), RECT_ARGS(mLocalBounds));
     }
 
@@ -978,7 +978,7 @@
     }
 
     virtual void output(int level, uint32_t logFlags) const {
-        OP_LOG("Draw patch "RECT_STRING, RECT_ARGS(mLocalBounds));
+        OP_LOG("Draw patch " RECT_STRING, RECT_ARGS(mLocalBounds));
     }
 
     virtual const char* name() { return "DrawPatch"; }
@@ -1060,7 +1060,7 @@
     }
 
     virtual void output(int level, uint32_t logFlags) const {
-        OP_LOG("Draw Rect "RECT_STRING, RECT_ARGS(mLocalBounds));
+        OP_LOG("Draw Rect " RECT_STRING, RECT_ARGS(mLocalBounds));
     }
 
     virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo,
@@ -1111,7 +1111,7 @@
     }
 
     virtual void output(int level, uint32_t logFlags) const {
-        OP_LOG("Draw RoundRect "RECT_STRING", rx %f, ry %f", RECT_ARGS(mLocalBounds), mRx, mRy);
+        OP_LOG("Draw RoundRect " RECT_STRING ", rx %f, ry %f", RECT_ARGS(mLocalBounds), mRx, mRy);
     }
 
     virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo,
@@ -1184,7 +1184,7 @@
     }
 
     virtual void output(int level, uint32_t logFlags) const {
-        OP_LOG("Draw Oval "RECT_STRING, RECT_ARGS(mLocalBounds));
+        OP_LOG("Draw Oval " RECT_STRING, RECT_ARGS(mLocalBounds));
     }
 
     virtual const char* name() { return "DrawOval"; }
@@ -1204,7 +1204,7 @@
     }
 
     virtual void output(int level, uint32_t logFlags) const {
-        OP_LOG("Draw Arc "RECT_STRING", start %f, sweep %f, useCenter %d",
+        OP_LOG("Draw Arc " RECT_STRING ", start %f, sweep %f, useCenter %d",
                 RECT_ARGS(mLocalBounds), mStartAngle, mSweepAngle, mUseCenter);
     }
 
@@ -1241,7 +1241,7 @@
     }
 
     virtual void output(int level, uint32_t logFlags) const {
-        OP_LOG("Draw Path %p in "RECT_STRING, mPath, RECT_ARGS(mLocalBounds));
+        OP_LOG("Draw Path %p in " RECT_STRING, mPath, RECT_ARGS(mLocalBounds));
     }
 
     virtual const char* name() { return "DrawPath"; }
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 229afdf..0e47c6e2 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -141,14 +141,12 @@
     StatefulBaseRenderer::skew(sx, sy);
 }
 
-void DisplayListRenderer::setMatrix(const SkMatrix* matrix) {
-    matrix = refMatrix(matrix);
+void DisplayListRenderer::setMatrix(const SkMatrix& matrix) {
     addStateOp(new (alloc()) SetMatrixOp(matrix));
     StatefulBaseRenderer::setMatrix(matrix);
 }
 
-void DisplayListRenderer::concatMatrix(const SkMatrix* matrix) {
-    matrix = refMatrix(matrix);
+void DisplayListRenderer::concatMatrix(const SkMatrix& matrix) {
     addStateOp(new (alloc()) ConcatMatrixOp(matrix));
     StatefulBaseRenderer::concatMatrix(matrix);
 }
@@ -203,10 +201,9 @@
     return DrawGlInfo::kStatusDone;
 }
 
-status_t DisplayListRenderer::drawBitmap(const SkBitmap* bitmap, const SkMatrix* matrix,
+status_t DisplayListRenderer::drawBitmap(const SkBitmap* bitmap, const SkMatrix& matrix,
         const SkPaint* paint) {
     bitmap = refBitmap(bitmap);
-    matrix = refMatrix(matrix);
     paint = refPaint(paint);
 
     addDrawOp(new (alloc()) DrawBitmapMatrixOp(bitmap, matrix, paint));
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index dff4f6c..2eaa671 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -86,8 +86,8 @@
     virtual void scale(float sx, float sy);
     virtual void skew(float sx, float sy);
 
-    virtual void setMatrix(const SkMatrix* matrix);
-    virtual void concatMatrix(const SkMatrix* matrix);
+    virtual void setMatrix(const SkMatrix& matrix);
+    virtual void concatMatrix(const SkMatrix& matrix);
 
     // Clip
     virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
@@ -106,7 +106,7 @@
     // Bitmap-based
     virtual status_t drawBitmap(const SkBitmap* bitmap, float left, float top,
             const SkPaint* paint);
-    virtual status_t drawBitmap(const SkBitmap* bitmap, const SkMatrix* matrix,
+    virtual status_t drawBitmap(const SkBitmap* bitmap, const SkMatrix& matrix,
             const SkPaint* paint);
     virtual status_t drawBitmap(const SkBitmap* bitmap, float srcLeft, float srcTop,
             float srcRight, float srcBottom, float dstLeft, float dstTop,
@@ -238,17 +238,6 @@
         return regionCopy;
     }
 
-    inline const SkMatrix* refMatrix(const SkMatrix* matrix) {
-        if (matrix) {
-            // Copying the matrix is cheap and prevents against the user changing
-            // the original matrix before the operation that uses it
-            const SkMatrix* copy = new SkMatrix(*matrix);
-            mDisplayListData->matrices.add(copy);
-            return copy;
-        }
-        return matrix;
-    }
-
     inline Layer* refLayer(Layer* layer) {
         mDisplayListData->layers.add(layer);
         mCaches.resourceCache.incrementRefcount(layer);
diff --git a/libs/hwui/DrawProfiler.cpp b/libs/hwui/DrawProfiler.cpp
index 971a66e..2409554 100644
--- a/libs/hwui/DrawProfiler.cpp
+++ b/libs/hwui/DrawProfiler.cpp
@@ -109,7 +109,7 @@
     mCurrentFrame = (mCurrentFrame + 1) % mDataSize;
 }
 
-void DrawProfiler::unionDirty(Rect* dirty) {
+void DrawProfiler::unionDirty(SkRect* dirty) {
     RETURN_IF_DISABLED();
     // Not worth worrying about minimizing the dirty region for debugging, so just
     // dirty the entire viewport.
diff --git a/libs/hwui/DrawProfiler.h b/libs/hwui/DrawProfiler.h
index c1aa1c6..7c06e5d 100644
--- a/libs/hwui/DrawProfiler.h
+++ b/libs/hwui/DrawProfiler.h
@@ -37,7 +37,7 @@
     void markPlaybackEnd();
     void finishFrame();
 
-    void unionDirty(Rect* dirty);
+    void unionDirty(SkRect* dirty);
     void draw(OpenGLRenderer* canvas);
 
     void dumpData(int fd);
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 4407ab0..bf0ab5c 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -592,7 +592,7 @@
 }
 
 FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, const char *text,
-        uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) {
+        uint32_t startIndex, uint32_t len, int numGlyphs, float radius, const float* positions) {
     checkInit();
 
     DropShadow image;
@@ -613,8 +613,9 @@
     Rect bounds;
     mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
 
-    uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
-    uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
+    uint32_t intRadius = Blur::convertRadiusToInt(radius);
+    uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * intRadius;
+    uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * intRadius;
 
     uint32_t maxSize = Caches::getInstance().maxTextureSize;
     if (paddedWidth > maxSize || paddedHeight > maxSize) {
@@ -635,8 +636,8 @@
 
     memset(dataBuffer, 0, size);
 
-    int penX = radius - bounds.left;
-    int penY = radius - bounds.bottom;
+    int penX = intRadius - bounds.left;
+    int penY = intRadius - bounds.bottom;
 
     if ((bounds.right > bounds.left) && (bounds.top > bounds.bottom)) {
         // text has non-whitespace, so draw and blur to create the shadow
@@ -727,9 +728,10 @@
     }
 }
 
-void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, int32_t radius) {
+void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, float radius) {
+    uint32_t intRadius = Blur::convertRadiusToInt(radius);
 #ifdef ANDROID_ENABLE_RENDERSCRIPT
-    if (width * height * radius >= RS_MIN_INPUT_CUTOFF) {
+    if (width * height * intRadius >= RS_MIN_INPUT_CUTOFF) {
         uint8_t* outImage = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height);
 
         if (mRs == 0) {
@@ -768,12 +770,12 @@
     }
 #endif
 
-    float *gaussian = new float[2 * radius + 1];
-    Blur::generateGaussianWeights(gaussian, radius);
+    float *gaussian = new float[2 * intRadius + 1];
+    Blur::generateGaussianWeights(gaussian, intRadius);
 
     uint8_t* scratch = new uint8_t[width * height];
-    Blur::horizontal(gaussian, radius, *image, scratch, width, height);
-    Blur::vertical(gaussian, radius, scratch, *image, width, height);
+    Blur::horizontal(gaussian, intRadius, *image, scratch, width, height);
+    Blur::vertical(gaussian, intRadius, scratch, *image, width, height);
 
     delete[] gaussian;
     delete[] scratch;
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 9259028..8ce22b0 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -129,7 +129,7 @@
     // After renderDropShadow returns, the called owns the memory in DropShadow.image
     // and is responsible for releasing it when it's done with it
     DropShadow renderDropShadow(const SkPaint* paint, const char *text, uint32_t startIndex,
-            uint32_t len, int numGlyphs, uint32_t radius, const float* positions);
+            uint32_t len, int numGlyphs, float radius, const float* positions);
 
     void setTextureFiltering(bool linearFiltering) {
         mLinearFiltering = linearFiltering;
@@ -218,7 +218,7 @@
             int32_t width, int32_t height);
 
     // the input image handle may have its pointer replaced (to avoid copies)
-    void blurImage(uint8_t** image, int32_t width, int32_t height, int32_t radius);
+    void blurImage(uint8_t** image, int32_t width, int32_t height, float radius);
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp
index 2268386..9f2014f 100644
--- a/libs/hwui/Matrix.cpp
+++ b/libs/hwui/Matrix.cpp
@@ -417,6 +417,8 @@
 }
 
 void Matrix4::mapRect(Rect& r) const {
+    if (isIdentity()) return;
+
     if (isSimple()) {
         MUL_ADD_STORE(r.left, data[kScaleX], data[kTranslateX]);
         MUL_ADD_STORE(r.right, data[kScaleX], data[kTranslateX]);
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index e33a001..1c5c578 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -147,6 +147,7 @@
             data[kTranslateX] += x;
             data[kTranslateY] += y;
             data[kTranslateZ] += z;
+            mType |= kTypeUnknown;
         } else {
             // Doing a translation will only affect the translate bit of the type
             // Save the type
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 31f399a..8f3872a 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2043,10 +2043,10 @@
     return DrawGlInfo::kStatusDrew;
 }
 
-status_t OpenGLRenderer::drawBitmap(const SkBitmap* bitmap, const SkMatrix* matrix,
+status_t OpenGLRenderer::drawBitmap(const SkBitmap* bitmap, const SkMatrix& matrix,
         const SkPaint* paint) {
     Rect r(0.0f, 0.0f, bitmap->width(), bitmap->height());
-    const mat4 transform(*matrix);
+    const mat4 transform(matrix);
     transform.mapRect(r);
 
     if (quickRejectSetupScissor(r.left, r.top, r.right, r.bottom)) {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index b9b369f..346a65c 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -164,7 +164,7 @@
             const SkPaint* paint);
     status_t drawBitmaps(const SkBitmap* bitmap, AssetAtlas::Entry* entry, int bitmapCount,
             TextureVertex* vertices, bool pureTranslate, const Rect& bounds, const SkPaint* paint);
-    virtual status_t drawBitmap(const SkBitmap* bitmap, const SkMatrix* matrix,
+    virtual status_t drawBitmap(const SkBitmap* bitmap, const SkMatrix& matrix,
             const SkPaint* paint);
     virtual status_t drawBitmap(const SkBitmap* bitmap, float srcLeft, float srcTop,
             float srcRight, float srcBottom, float dstLeft, float dstTop,
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index ab6b742..9dd5aa5 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -339,7 +339,7 @@
 
     float left, top, offset;
     uint32_t width, height;
-    PathCache::computePathBounds(t->path, t->paint, left, top, offset, width, height);
+    PathCache::computePathBounds(t->path, &t->paint, left, top, offset, width, height);
 
     PathTexture* texture = t->texture;
     texture->left = left;
@@ -350,7 +350,7 @@
 
     if (width <= mMaxTextureSize && height <= mMaxTextureSize) {
         SkBitmap* bitmap = new SkBitmap();
-        drawPath(t->path, t->paint, *bitmap, left, top, offset, width, height);
+        drawPath(t->path, &t->paint, *bitmap, left, top, offset, width, height);
         t->setResult(bitmap);
     } else {
         texture->width = 0;
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index 6177ff1..eee138b 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -272,7 +272,7 @@
     class PathTask: public Task<SkBitmap*> {
     public:
         PathTask(const SkPath* path, const SkPaint* paint, PathTexture* texture):
-            path(path), paint(paint), texture(texture) {
+            path(path), paint(*paint), texture(texture) {
         }
 
         ~PathTask() {
@@ -280,7 +280,8 @@
         }
 
         const SkPath* path;
-        const SkPaint* paint;
+        //copied, since input paint may not be immutable
+        const SkPaint paint;
         PathTexture* texture;
     };
 
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index d964efc..83ad76f 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -15,6 +15,7 @@
  */
 
 #define ATRACE_TAG ATRACE_TAG_VIEW
+#define LOG_TAG "RenderNode"
 
 #include "RenderNode.h"
 
@@ -25,6 +26,7 @@
 
 #include <utils/Trace.h>
 
+#include "DamageAccumulator.h"
 #include "Debug.h"
 #include "DisplayListOp.h"
 #include "DisplayListLogBuffer.h"
@@ -110,14 +112,30 @@
     prepareTreeImpl(info);
 }
 
-void RenderNode::prepareTreeImpl(TreeInfo& info) {
-    if (info.performStagingPush) {
-        pushStagingChanges(info);
+void RenderNode::damageSelf(TreeInfo& info) {
+    if (isRenderable() && properties().getAlpha() > 0) {
+        if (properties().getClipToBounds()) {
+            info.damageAccumulator->dirty(0, 0, properties().getWidth(), properties().getHeight());
+        } else {
+            // Hope this is big enough?
+            // TODO: Get this from the display list ops or something
+            info.damageAccumulator->dirty(INT_MIN, INT_MIN, INT_MAX, INT_MAX);
+        }
     }
-    if (info.evaluateAnimations) {
+}
+
+void RenderNode::prepareTreeImpl(TreeInfo& info) {
+    info.damageAccumulator->pushTransform(this);
+    if (info.mode == TreeInfo::MODE_FULL) {
+        pushStagingChanges(info);
+        evaluateAnimations(info);
+    } else if (info.mode == TreeInfo::MODE_MAYBE_DETACHING) {
+        pushStagingChanges(info);
+    } else if (info.mode == TreeInfo::MODE_RT_ONLY) {
         evaluateAnimations(info);
     }
     prepareSubTree(info, mDisplayListData);
+    info.damageAccumulator->popTransform();
 }
 
 class PushAnimatorsFunctor {
@@ -149,18 +167,28 @@
     }
     if (mDirtyPropertyFields) {
         mDirtyPropertyFields = 0;
+        damageSelf(info);
+        info.damageAccumulator->popTransform();
         mProperties = mStagingProperties;
+        // We could try to be clever and only re-damage if the matrix changed.
+        // However, we don't need to worry about that. The cost of over-damaging
+        // here is only going to be a single additional map rect of this node
+        // plus a rect join(). The parent's transform (and up) will only be
+        // performed once.
+        info.damageAccumulator->pushTransform(this);
+        damageSelf(info);
     }
     if (mNeedsDisplayListDataSync) {
         mNeedsDisplayListDataSync = false;
         // Do a push pass on the old tree to handle freeing DisplayListData
         // that are no longer used
-        TreeInfo oldTreeInfo;
+        TreeInfo oldTreeInfo(TreeInfo::MODE_MAYBE_DETACHING);
+        oldTreeInfo.damageAccumulator = info.damageAccumulator;
         prepareSubTree(oldTreeInfo, mDisplayListData);
-        // TODO: The damage for the old tree should be accounted for
         delete mDisplayListData;
         mDisplayListData = mStagingDisplayListData;
         mStagingDisplayListData = 0;
+        damageSelf(info);
     }
 }
 
@@ -180,12 +208,21 @@
 void RenderNode::evaluateAnimations(TreeInfo& info) {
     if (!mAnimators.size()) return;
 
+    // TODO: Can we target this better? For now treat it like any other staging
+    // property push and just damage self before and after animators are run
+
+    damageSelf(info);
+    info.damageAccumulator->popTransform();
+
     AnimateFunctor functor(this, info);
     std::vector< sp<BaseRenderNodeAnimator> >::iterator newEnd;
     newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
     mAnimators.erase(newEnd, mAnimators.end());
     mProperties.updateMatrix();
     info.out.hasAnimations |= mAnimators.size();
+
+    info.damageAccumulator->pushTransform(this);
+    damageSelf(info);
 }
 
 void RenderNode::prepareSubTree(TreeInfo& info, DisplayListData* subtree) {
@@ -201,8 +238,11 @@
             info.prepareTextures = cache.prefetchAndMarkInUse(subtree->bitmapResources[i]);
         }
         for (size_t i = 0; i < subtree->children().size(); i++) {
-            RenderNode* childNode = subtree->children()[i]->mDisplayList;
+            DrawDisplayListOp* op = subtree->children()[i];
+            RenderNode* childNode = op->mDisplayList;
+            info.damageAccumulator->pushTransform(&op->mTransformFromParent);
             childNode->prepareTreeImpl(info);
+            info.damageAccumulator->popTransform();
         }
     }
 }
@@ -223,9 +263,9 @@
         renderer.translate(properties().getLeft(), properties().getTop());
     }
     if (properties().getStaticMatrix()) {
-        renderer.concatMatrix(properties().getStaticMatrix());
+        renderer.concatMatrix(*properties().getStaticMatrix());
     } else if (properties().getAnimationMatrix()) {
-        renderer.concatMatrix(properties().getAnimationMatrix());
+        renderer.concatMatrix(*properties().getAnimationMatrix());
     }
     if (properties().hasTransformMatrix()) {
         if (properties().isTransformTranslateOnly()) {
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 1a5377b..f0f6e7c 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -39,6 +39,7 @@
 
 #include <androidfw/ResourceTypes.h>
 
+#include "DamageAccumulator.h"
 #include "Debug.h"
 #include "Matrix.h"
 #include "DeferredDisplayList.h"
@@ -125,6 +126,10 @@
         return mDisplayListData && mDisplayListData->hasDrawOps;
     }
 
+    bool hasProjectionReceiver() const {
+        return mDisplayListData && mDisplayListData->projectionReceiveIndex >= 0;
+    }
+
     const char* getName() const {
         return mName.string();
     }
@@ -148,7 +153,7 @@
         mDirtyPropertyFields |= fields;
     }
 
-    const RenderProperties& properties() {
+    const RenderProperties& properties() const {
         return mProperties;
     }
 
@@ -187,6 +192,9 @@
         mNeedsAnimatorsSync = true;
     }
 
+protected:
+    virtual void damageSelf(TreeInfo& info);
+
 private:
     typedef key_value_pair_t<float, DrawDisplayListOp*> ZDrawDisplayListOpPair;
 
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index c0e3ce7..b012fc5 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -40,6 +40,10 @@
 class Matrix4;
 class RenderNode;
 
+// The __VA_ARGS__ will be executed if a & b are not equal
+#define RP_SET(a, b, ...) (a != b ? (a = b, ##__VA_ARGS__, true) : false)
+#define RP_SET_AND_DIRTY(a, b) RP_SET(a, b, mPrimitiveFields.mMatrixOrPivotDirty = true)
+
 /*
  * Data structure that holds the properties for a RenderNode
  */
@@ -50,29 +54,30 @@
 
     RenderProperties& operator=(const RenderProperties& other);
 
-    void setClipToBounds(bool clipToBounds) {
-        mPrimitiveFields.mClipToBounds = clipToBounds;
+    bool setClipToBounds(bool clipToBounds) {
+        return RP_SET(mPrimitiveFields.mClipToBounds, clipToBounds);
     }
 
-    void setProjectBackwards(bool shouldProject) {
-        mPrimitiveFields.mProjectBackwards = shouldProject;
+    bool setProjectBackwards(bool shouldProject) {
+        return RP_SET(mPrimitiveFields.mProjectBackwards, shouldProject);
     }
 
-    void setProjectionReceiver(bool shouldRecieve) {
-        mPrimitiveFields.mProjectionReceiver = shouldRecieve;
+    bool setProjectionReceiver(bool shouldRecieve) {
+        return RP_SET(mPrimitiveFields.mProjectionReceiver, shouldRecieve);
     }
 
     bool isProjectionReceiver() const {
         return mPrimitiveFields.mProjectionReceiver;
     }
 
-    void setStaticMatrix(const SkMatrix* matrix) {
+    bool setStaticMatrix(const SkMatrix* matrix) {
         delete mStaticMatrix;
         if (matrix) {
             mStaticMatrix = new SkMatrix(*matrix);
         } else {
             mStaticMatrix = NULL;
         }
+        return true;
     }
 
     // Can return NULL
@@ -80,72 +85,61 @@
         return mStaticMatrix;
     }
 
-    void setAnimationMatrix(const SkMatrix* matrix) {
+    bool setAnimationMatrix(const SkMatrix* matrix) {
         delete mAnimationMatrix;
         if (matrix) {
             mAnimationMatrix = new SkMatrix(*matrix);
         } else {
             mAnimationMatrix = NULL;
         }
+        return true;
     }
 
-    void setAlpha(float alpha) {
+    bool setAlpha(float alpha) {
         alpha = fminf(1.0f, fmaxf(0.0f, alpha));
-        if (alpha != mPrimitiveFields.mAlpha) {
-            mPrimitiveFields.mAlpha = alpha;
-        }
+        return RP_SET(mPrimitiveFields.mAlpha, alpha);
     }
 
     float getAlpha() const {
         return mPrimitiveFields.mAlpha;
     }
 
-    void setHasOverlappingRendering(bool hasOverlappingRendering) {
-        mPrimitiveFields.mHasOverlappingRendering = hasOverlappingRendering;
+    bool setHasOverlappingRendering(bool hasOverlappingRendering) {
+        return RP_SET(mPrimitiveFields.mHasOverlappingRendering, hasOverlappingRendering);
     }
 
     bool hasOverlappingRendering() const {
         return mPrimitiveFields.mHasOverlappingRendering;
     }
 
-    void setElevation(float elevation) {
-        if (elevation != mPrimitiveFields.mElevation) {
-            mPrimitiveFields.mElevation = elevation;
-            // mMatrixOrPivotDirty not set, since matrix doesn't respect Z
-        }
+    bool setElevation(float elevation) {
+        return RP_SET(mPrimitiveFields.mElevation, elevation);
+        // Don't dirty matrix/pivot, since they don't respect Z
     }
 
     float getElevation() const {
         return mPrimitiveFields.mElevation;
     }
 
-    void setTranslationX(float translationX) {
-        if (translationX != mPrimitiveFields.mTranslationX) {
-            mPrimitiveFields.mTranslationX = translationX;
-            mPrimitiveFields.mMatrixOrPivotDirty = true;
-        }
+    bool setTranslationX(float translationX) {
+        return RP_SET_AND_DIRTY(mPrimitiveFields.mTranslationX, translationX);
     }
 
     float getTranslationX() const {
         return mPrimitiveFields.mTranslationX;
     }
 
-    void setTranslationY(float translationY) {
-        if (translationY != mPrimitiveFields.mTranslationY) {
-            mPrimitiveFields.mTranslationY = translationY;
-            mPrimitiveFields.mMatrixOrPivotDirty = true;
-        }
+    bool setTranslationY(float translationY) {
+        return RP_SET_AND_DIRTY(mPrimitiveFields.mTranslationY, translationY);
     }
 
     float getTranslationY() const {
         return mPrimitiveFields.mTranslationY;
     }
 
-    void setTranslationZ(float translationZ) {
-        if (translationZ != mPrimitiveFields.mTranslationZ) {
-            mPrimitiveFields.mTranslationZ = translationZ;
-            // mMatrixOrPivotDirty not set, since matrix doesn't respect Z
-        }
+    bool setTranslationZ(float translationZ) {
+        return RP_SET(mPrimitiveFields.mTranslationZ, translationZ);
+        // mMatrixOrPivotDirty not set, since matrix doesn't respect Z
     }
 
     float getTranslationZ() const {
@@ -153,8 +147,8 @@
     }
 
     // Animation helper
-    void setX(float value) {
-        setTranslationX(value - getLeft());
+    bool setX(float value) {
+        return setTranslationX(value - getLeft());
     }
 
     // Animation helper
@@ -163,8 +157,8 @@
     }
 
     // Animation helper
-    void setY(float value) {
-        setTranslationY(value - getTop());
+    bool setY(float value) {
+        return setTranslationY(value - getTop());
     }
 
     // Animation helper
@@ -173,87 +167,80 @@
     }
 
     // Animation helper
-    void setZ(float value) {
-        setTranslationZ(value - getElevation());
+    bool setZ(float value) {
+        return setTranslationZ(value - getElevation());
     }
 
     float getZ() const {
         return getElevation() + getTranslationZ();
     }
 
-    void setRotation(float rotation) {
-        if (rotation != mPrimitiveFields.mRotation) {
-            mPrimitiveFields.mRotation = rotation;
-            mPrimitiveFields.mMatrixOrPivotDirty = true;
-        }
+    bool setRotation(float rotation) {
+        return RP_SET_AND_DIRTY(mPrimitiveFields.mRotation, rotation);
     }
 
     float getRotation() const {
         return mPrimitiveFields.mRotation;
     }
 
-    void setRotationX(float rotationX) {
-        if (rotationX != mPrimitiveFields.mRotationX) {
-            mPrimitiveFields.mRotationX = rotationX;
-            mPrimitiveFields.mMatrixOrPivotDirty = true;
-        }
+    bool setRotationX(float rotationX) {
+        return RP_SET_AND_DIRTY(mPrimitiveFields.mRotationX, rotationX);
     }
 
     float getRotationX() const {
         return mPrimitiveFields.mRotationX;
     }
 
-    void setRotationY(float rotationY) {
-        if (rotationY != mPrimitiveFields.mRotationY) {
-            mPrimitiveFields.mRotationY = rotationY;
-            mPrimitiveFields.mMatrixOrPivotDirty = true;
-        }
+    bool setRotationY(float rotationY) {
+        return RP_SET_AND_DIRTY(mPrimitiveFields.mRotationY, rotationY);
     }
 
     float getRotationY() const {
         return mPrimitiveFields.mRotationY;
     }
 
-    void setScaleX(float scaleX) {
-        if (scaleX != mPrimitiveFields.mScaleX) {
-            mPrimitiveFields.mScaleX = scaleX;
-            mPrimitiveFields.mMatrixOrPivotDirty = true;
-        }
+    bool setScaleX(float scaleX) {
+        return RP_SET_AND_DIRTY(mPrimitiveFields.mScaleX, scaleX);
     }
 
     float getScaleX() const {
         return mPrimitiveFields.mScaleX;
     }
 
-    void setScaleY(float scaleY) {
-        if (scaleY != mPrimitiveFields.mScaleY) {
-            mPrimitiveFields.mScaleY = scaleY;
-            mPrimitiveFields.mMatrixOrPivotDirty = true;
-        }
+    bool setScaleY(float scaleY) {
+        return RP_SET_AND_DIRTY(mPrimitiveFields.mScaleY, scaleY);
     }
 
     float getScaleY() const {
         return mPrimitiveFields.mScaleY;
     }
 
-    void setPivotX(float pivotX) {
-        mPrimitiveFields.mPivotX = pivotX;
-        mPrimitiveFields.mMatrixOrPivotDirty = true;
-        mPrimitiveFields.mPivotExplicitlySet = true;
+    bool setPivotX(float pivotX) {
+        if (RP_SET(mPrimitiveFields.mPivotX, pivotX)
+                || !mPrimitiveFields.mPivotExplicitlySet) {
+            mPrimitiveFields.mMatrixOrPivotDirty = true;
+            mPrimitiveFields.mPivotExplicitlySet = true;
+            return true;
+        }
+        return false;
     }
 
     /* Note that getPivotX and getPivotY are adjusted by updateMatrix(),
-     * so the value returned mPrimitiveFields.may be stale if the RenderProperties has been
-     * mPrimitiveFields.modified since the last call to updateMatrix()
+     * so the value returned may be stale if the RenderProperties has been
+     * modified since the last call to updateMatrix()
      */
     float getPivotX() const {
         return mPrimitiveFields.mPivotX;
     }
 
-    void setPivotY(float pivotY) {
-        mPrimitiveFields.mPivotY = pivotY;
-        mPrimitiveFields.mMatrixOrPivotDirty = true;
-        mPrimitiveFields.mPivotExplicitlySet = true;
+    bool setPivotY(float pivotY) {
+        if (RP_SET(mPrimitiveFields.mPivotY, pivotY)
+                || !mPrimitiveFields.mPivotExplicitlySet) {
+            mPrimitiveFields.mMatrixOrPivotDirty = true;
+            mPrimitiveFields.mPivotExplicitlySet = true;
+            return true;
+        }
+        return false;
     }
 
     float getPivotY() const {
@@ -264,11 +251,13 @@
         return mPrimitiveFields.mPivotExplicitlySet;
     }
 
-    void setCameraDistance(float distance) {
+    bool setCameraDistance(float distance) {
         if (distance != getCameraDistance()) {
             mPrimitiveFields.mMatrixOrPivotDirty = true;
             mComputedFields.mTransformCamera.setCameraLocation(0, 0, distance);
+            return true;
         }
+        return false;
     }
 
     float getCameraDistance() const {
@@ -276,75 +265,73 @@
         return const_cast<Sk3DView*>(&mComputedFields.mTransformCamera)->getCameraLocationZ();
     }
 
-    void setLeft(int left) {
-        if (left != mPrimitiveFields.mLeft) {
-            mPrimitiveFields.mLeft = left;
+    bool setLeft(int left) {
+        if (RP_SET(mPrimitiveFields.mLeft, left)) {
             mPrimitiveFields.mWidth = mPrimitiveFields.mRight - mPrimitiveFields.mLeft;
             if (!mPrimitiveFields.mPivotExplicitlySet) {
                 mPrimitiveFields.mMatrixOrPivotDirty = true;
             }
+            return true;
         }
+        return false;
     }
 
     float getLeft() const {
         return mPrimitiveFields.mLeft;
     }
 
-    void setTop(int top) {
-        if (top != mPrimitiveFields.mTop) {
-            mPrimitiveFields.mTop = top;
+    bool setTop(int top) {
+        if (RP_SET(mPrimitiveFields.mTop, top)) {
             mPrimitiveFields.mHeight = mPrimitiveFields.mBottom - mPrimitiveFields.mTop;
             if (!mPrimitiveFields.mPivotExplicitlySet) {
                 mPrimitiveFields.mMatrixOrPivotDirty = true;
             }
+            return true;
         }
+        return false;
     }
 
     float getTop() const {
         return mPrimitiveFields.mTop;
     }
 
-    void setRight(int right) {
-        if (right != mPrimitiveFields.mRight) {
-            mPrimitiveFields.mRight = right;
+    bool setRight(int right) {
+        if (RP_SET(mPrimitiveFields.mRight, right)) {
             mPrimitiveFields.mWidth = mPrimitiveFields.mRight - mPrimitiveFields.mLeft;
             if (!mPrimitiveFields.mPivotExplicitlySet) {
                 mPrimitiveFields.mMatrixOrPivotDirty = true;
             }
+            return true;
         }
+        return false;
     }
 
     float getRight() const {
         return mPrimitiveFields.mRight;
     }
 
-    void setBottom(int bottom) {
-        if (bottom != mPrimitiveFields.mBottom) {
-            mPrimitiveFields.mBottom = bottom;
+    bool setBottom(int bottom) {
+        if (RP_SET(mPrimitiveFields.mBottom, bottom)) {
             mPrimitiveFields.mHeight = mPrimitiveFields.mBottom - mPrimitiveFields.mTop;
             if (!mPrimitiveFields.mPivotExplicitlySet) {
                 mPrimitiveFields.mMatrixOrPivotDirty = true;
             }
+            return true;
         }
+        return false;
     }
 
     float getBottom() const {
         return mPrimitiveFields.mBottom;
     }
 
-    void setLeftTop(int left, int top) {
-        if (left != mPrimitiveFields.mLeft || top != mPrimitiveFields.mTop) {
-            mPrimitiveFields.mLeft = left;
-            mPrimitiveFields.mTop = top;
-            mPrimitiveFields.mWidth = mPrimitiveFields.mRight - mPrimitiveFields.mLeft;
-            mPrimitiveFields.mHeight = mPrimitiveFields.mBottom - mPrimitiveFields.mTop;
-            if (!mPrimitiveFields.mPivotExplicitlySet) {
-                mPrimitiveFields.mMatrixOrPivotDirty = true;
-            }
-        }
+    bool setLeftTop(int left, int top) {
+        bool leftResult = setLeft(left);
+        bool topResult = setTop(top);
+        return leftResult || topResult;
     }
 
-    void setLeftTopRightBottom(int left, int top, int right, int bottom) {
+    bool setLeftTopRightBottom(int left, int top, int right, int bottom) {
         if (left != mPrimitiveFields.mLeft || top != mPrimitiveFields.mTop
                 || right != mPrimitiveFields.mRight || bottom != mPrimitiveFields.mBottom) {
             mPrimitiveFields.mLeft = left;
@@ -356,31 +343,31 @@
             if (!mPrimitiveFields.mPivotExplicitlySet) {
                 mPrimitiveFields.mMatrixOrPivotDirty = true;
             }
+            return true;
         }
+        return false;
     }
 
-    void offsetLeftRight(float offset) {
+    bool offsetLeftRight(float offset) {
         if (offset != 0) {
             mPrimitiveFields.mLeft += offset;
             mPrimitiveFields.mRight += offset;
-            if (!mPrimitiveFields.mPivotExplicitlySet) {
-                mPrimitiveFields.mMatrixOrPivotDirty = true;
-            }
+            return true;
         }
+        return false;
     }
 
-    void offsetTopBottom(float offset) {
+    bool offsetTopBottom(float offset) {
         if (offset != 0) {
             mPrimitiveFields.mTop += offset;
             mPrimitiveFields.mBottom += offset;
-            if (!mPrimitiveFields.mPivotExplicitlySet) {
-                mPrimitiveFields.mMatrixOrPivotDirty = true;
-            }
+            return true;
         }
+        return false;
     }
 
-    void setCaching(bool caching) {
-        mPrimitiveFields.mCaching = caching;
+    bool setCaching(bool caching) {
+        return RP_SET(mPrimitiveFields.mCaching, caching);
     }
 
     int getWidth() const {
diff --git a/libs/hwui/Renderer.h b/libs/hwui/Renderer.h
index 23cab0e..320895c 100644
--- a/libs/hwui/Renderer.h
+++ b/libs/hwui/Renderer.h
@@ -170,8 +170,8 @@
     virtual void scale(float sx, float sy) = 0;
     virtual void skew(float sx, float sy) = 0;
 
-    virtual void setMatrix(const SkMatrix* matrix) = 0;
-    virtual void concatMatrix(const SkMatrix* matrix) = 0;
+    virtual void setMatrix(const SkMatrix& matrix) = 0;
+    virtual void concatMatrix(const SkMatrix& matrix) = 0;
 
     // clip
     virtual const Rect& getLocalClipBounds() const = 0;
@@ -193,7 +193,7 @@
     // Bitmap-based
     virtual status_t drawBitmap(const SkBitmap* bitmap, float left, float top,
             const SkPaint* paint) = 0;
-    virtual status_t drawBitmap(const SkBitmap* bitmap, const SkMatrix* matrix,
+    virtual status_t drawBitmap(const SkBitmap* bitmap, const SkMatrix& matrix,
             const SkPaint* paint) = 0;
     virtual status_t drawBitmap(const SkBitmap* bitmap, float srcLeft, float srcTop,
             float srcRight, float srcBottom, float dstLeft, float dstTop,
diff --git a/libs/hwui/StatefulBaseRenderer.cpp b/libs/hwui/StatefulBaseRenderer.cpp
index f2e28e1..95c0ee5 100644
--- a/libs/hwui/StatefulBaseRenderer.cpp
+++ b/libs/hwui/StatefulBaseRenderer.cpp
@@ -125,20 +125,16 @@
     mSnapshot->transform->skew(sx, sy);
 }
 
-void StatefulBaseRenderer::setMatrix(const SkMatrix* matrix) {
-    if (matrix) {
-        mSnapshot->transform->load(*matrix);
-    } else {
-        mSnapshot->transform->loadIdentity();
-    }
+void StatefulBaseRenderer::setMatrix(const SkMatrix& matrix) {
+    mSnapshot->transform->load(matrix);
 }
 
 void StatefulBaseRenderer::setMatrix(const Matrix4& matrix) {
     mSnapshot->transform->load(matrix);
 }
 
-void StatefulBaseRenderer::concatMatrix(const SkMatrix* matrix) {
-    mat4 transform(*matrix);
+void StatefulBaseRenderer::concatMatrix(const SkMatrix& matrix) {
+    mat4 transform(matrix);
     mSnapshot->transform->multiply(transform);
 }
 
diff --git a/libs/hwui/StatefulBaseRenderer.h b/libs/hwui/StatefulBaseRenderer.h
index dbb1d85..e8e024f 100644
--- a/libs/hwui/StatefulBaseRenderer.h
+++ b/libs/hwui/StatefulBaseRenderer.h
@@ -76,9 +76,9 @@
     virtual void scale(float sx, float sy);
     virtual void skew(float sx, float sy);
 
-    virtual void setMatrix(const SkMatrix* matrix);
+    virtual void setMatrix(const SkMatrix& matrix);
     void setMatrix(const Matrix4& matrix); // internal only convenience method
-    virtual void concatMatrix(const SkMatrix* matrix);
+    virtual void concatMatrix(const SkMatrix& matrix);
     void concatMatrix(const Matrix4& matrix); // internal only convenience method
 
     // Clip
diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp
index e6bc873..79fe4d3 100644
--- a/libs/hwui/TessellationCache.cpp
+++ b/libs/hwui/TessellationCache.cpp
@@ -350,12 +350,11 @@
 
 
 void TessellationCache::trim() {
-    // uint32_t size = getSize();
-    // while (size > mMaxSize) {
-    //     size -= mCache.peekOldestValue()->getSize();
-    //     mCache.removeOldest();
-    // }
-    mCache.clear(); // Workaround caching tessellation corruption
+    uint32_t size = getSize();
+    while (size > mMaxSize) {
+        size -= mCache.peekOldestValue()->getSize();
+        mCache.removeOldest();
+    }
     mShadowCache.clear();
 }
 
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 8355f83..fd78f8e 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -18,6 +18,9 @@
 
 #include <utils/Timers.h>
 
+#include "DamageAccumulator.h"
+#include "utils/Macros.h"
+
 namespace android {
 namespace uirenderer {
 
@@ -31,21 +34,45 @@
     ~AnimationHook() {}
 };
 
-struct TreeInfo {
-    // The defaults here should be safe for everyone but DrawFrameTask to use as-is.
-    TreeInfo()
-        : frameTimeMs(0)
+// This would be a struct, but we want to PREVENT_COPY_AND_ASSIGN
+class TreeInfo {
+    PREVENT_COPY_AND_ASSIGN(TreeInfo);
+public:
+    enum TraversalMode {
+        // The full monty - sync, push, run animators, etc... Used by DrawFrameTask
+        // May only be used if both the UI thread and RT thread are blocked on the
+        // prepare
+        MODE_FULL,
+        // Run only what can be done safely on RT thread. Currently this only means
+        // animators, but potentially things like SurfaceTexture updates
+        // could be handled by this as well if there are no listeners
+        MODE_RT_ONLY,
+        // The subtree is being detached. Maybe. If the RenderNode is present
+        // in both the old and new display list's children then it will get a
+        // MODE_MAYBE_DETACHING followed shortly by a MODE_FULL.
+        // Push any pending display list changes in case it is detached,
+        // but don't evaluate animators and such as if it isn't detached as a
+        // MODE_FULL will follow shortly.
+        MODE_MAYBE_DETACHING,
+        // TODO: TRIM_MEMORY?
+    };
+
+    explicit TreeInfo(TraversalMode mode)
+        : mode(mode)
+        , frameTimeMs(0)
         , animationHook(NULL)
-        , prepareTextures(false)
-        , performStagingPush(true)
-        , evaluateAnimations(false)
+        , prepareTextures(mode == MODE_FULL)
+        , damageAccumulator(NullDamageAccumulator::instance())
     {}
 
+    const TraversalMode mode;
     nsecs_t frameTimeMs;
     AnimationHook* animationHook;
+    // TODO: Remove this? Currently this is used to signal to stop preparing
+    // textures if we run out of cache space.
     bool prepareTextures;
-    bool performStagingPush;
-    bool evaluateAnimations;
+    // Must not be null
+    IDamageAccumulator* damageAccumulator;
 
     struct Out {
         Out()
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 9ebee1d..8a5c857 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -439,6 +439,7 @@
     mRenderThread.removeFrameCallback(this);
 
     info.frameTimeMs = mRenderThread.timeLord().frameTimeMs();
+    info.damageAccumulator = &mDamageAccumulator;
     mRootRenderNode->prepareTree(info);
 
     int runningBehind = 0;
@@ -465,27 +466,30 @@
     mRenderThread.pushBackFrameCallback(this);
 }
 
-void CanvasContext::draw(Rect* dirty) {
+void CanvasContext::draw() {
     LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE,
             "drawDisplayList called on a context with no canvas or surface!");
 
     profiler().markPlaybackStart();
 
+    SkRect dirty;
+    mDamageAccumulator.finish(&dirty);
+
     EGLint width, height;
     mGlobalContext->beginFrame(mEglSurface, &width, &height);
     if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) {
         mCanvas->setViewport(width, height);
-        dirty = NULL;
+        dirty.setEmpty();
     } else if (!mDirtyRegionsEnabled || mHaveNewSurface) {
-        dirty = NULL;
+        dirty.setEmpty();
     } else {
-        profiler().unionDirty(dirty);
+        profiler().unionDirty(&dirty);
     }
 
     status_t status;
-    if (dirty && !dirty->isEmpty()) {
-        status = mCanvas->prepareDirty(dirty->left, dirty->top,
-                dirty->right, dirty->bottom, mOpaque);
+    if (!dirty.isEmpty()) {
+        status = mCanvas->prepareDirty(dirty.fLeft, dirty.fTop,
+                dirty.fRight, dirty.fBottom, mOpaque);
     } else {
         status = mCanvas->prepare(mOpaque);
     }
@@ -516,14 +520,12 @@
 
     profiler().startFrame();
 
-    TreeInfo info;
-    info.evaluateAnimations = true;
-    info.performStagingPush = false;
+    TreeInfo info(TreeInfo::MODE_RT_ONLY);
     info.prepareTextures = false;
 
     prepareTree(info);
     if (info.out.canDrawThisFrame) {
-        draw(NULL);
+        draw();
     }
 }
 
@@ -543,7 +545,7 @@
 
 bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) {
     requireGlContext();
-    TreeInfo info;
+    TreeInfo info(TreeInfo::MODE_FULL);
     layer->apply(info);
     return LayerRenderer::copyLayer(layer->backingLayer(), bitmap);
 }
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 00c5bf0..d926b38 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -23,6 +23,7 @@
 #include <utils/Functor.h>
 #include <utils/Vector.h>
 
+#include "../DamageAccumulator.h"
 #include "../DrawProfiler.h"
 #include "../RenderNode.h"
 #include "RenderTask.h"
@@ -57,7 +58,7 @@
     void makeCurrent();
     void processLayerUpdate(DeferredLayerUpdater* layerUpdater, TreeInfo& info);
     void prepareTree(TreeInfo& info);
-    void draw(Rect* dirty);
+    void draw();
     void destroyCanvasAndSurface();
 
     // IFrameCallback, Chroreographer-driven frame callback entry point
@@ -99,6 +100,7 @@
     bool mOpaque;
     OpenGLRenderer* mCanvas;
     bool mHaveNewSurface;
+    DamageAccumulator mDamageAccumulator;
 
     const sp<RenderNode> mRootRenderNode;
 
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 61d67ca..bdfdd21 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -68,10 +68,6 @@
     }
 }
 
-void DrawFrameTask::setDirty(int left, int top, int right, int bottom) {
-    mDirty.set(left, top, right, bottom);
-}
-
 int DrawFrameTask::drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos) {
     LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
 
@@ -83,7 +79,6 @@
     // Reset the single-frame data
     mFrameTimeNanos = 0;
     mRecordDurationNanos = 0;
-    mDirty.setEmpty();
 
     return mSyncResult;
 }
@@ -103,13 +98,12 @@
     bool canUnblockUiThread;
     bool canDrawThisFrame;
     {
-        TreeInfo info;
+        TreeInfo info(TreeInfo::MODE_FULL);
         canUnblockUiThread = syncFrameState(info);
         canDrawThisFrame = info.out.canDrawThisFrame;
     }
 
     // Grab a copy of everything we need
-    Rect dirty(mDirty);
     CanvasContext* context = mContext;
 
     // From this point on anything in "this" is *UNSAFE TO ACCESS*
@@ -118,7 +112,7 @@
     }
 
     if (CC_LIKELY(canDrawThisFrame)) {
-        context->draw(&dirty);
+        context->draw();
     }
 
     if (!canUnblockUiThread) {
@@ -126,18 +120,11 @@
     }
 }
 
-static void initTreeInfo(TreeInfo& info) {
-    info.prepareTextures = true;
-    info.performStagingPush = true;
-    info.evaluateAnimations = true;
-}
-
 bool DrawFrameTask::syncFrameState(TreeInfo& info) {
     ATRACE_CALL();
     mRenderThread->timeLord().vsyncReceived(mFrameTimeNanos);
     mContext->makeCurrent();
     Caches::getInstance().textureCache.resetMarkInUse();
-    initTreeInfo(info);
 
     for (size_t i = 0; i < mLayers.size(); i++) {
         mContext->processLayerUpdate(mLayers[i].get(), info);
@@ -149,8 +136,6 @@
     mContext->prepareTree(info);
 
     if (info.out.hasAnimations) {
-        // TODO: dirty calculations, for now just do a full-screen inval
-        mDirty.setEmpty();
         if (info.out.requiresUiRedraw) {
             mSyncResult |= kSync_UIRedrawRequired;
         }
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index d4129b6..96f0add 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -60,7 +60,6 @@
     void pushLayerUpdate(DeferredLayerUpdater* layer);
     void removeLayerUpdate(DeferredLayerUpdater* layer);
 
-    void setDirty(int left, int top, int right, int bottom);
     void setDensity(float density) { mDensity = density; }
     int drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos);
 
@@ -80,7 +79,6 @@
     /*********************************************
      *  Single frame data
      *********************************************/
-    Rect mDirty;
     nsecs_t mFrameTimeNanos;
     nsecs_t mRecordDurationNanos;
     float mDensity;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 0901963..4988f19 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -47,7 +47,7 @@
 
 #define SETUP_TASK(method) \
     LOG_ALWAYS_FATAL_IF( METHOD_INVOKE_PAYLOAD_SIZE < sizeof(ARGS(method)), \
-        "METHOD_INVOKE_PAYLOAD_SIZE %d is smaller than sizeof(" #method "Args) %d", \
+        "METHOD_INVOKE_PAYLOAD_SIZE %zu is smaller than sizeof(" #method "Args) %zu", \
                 METHOD_INVOKE_PAYLOAD_SIZE, sizeof(ARGS(method))); \
     MethodInvokeRenderTask* task = new MethodInvokeRenderTask((RunnableMethod) Bridge_ ## method); \
     ARGS(method) *args = (ARGS(method) *) task->payload()
@@ -180,8 +180,7 @@
 }
 
 int RenderProxy::syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos,
-        float density, int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom) {
-    mDrawFrameTask.setDirty(dirtyLeft, dirtyTop, dirtyRight, dirtyBottom);
+        float density) {
     mDrawFrameTask.setDensity(density);
     return mDrawFrameTask.drawFrame(frameTimeNanos, recordDurationNanos);
 }
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 944ff9c..a95f8f0 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -70,7 +70,7 @@
     ANDROID_API void setup(int width, int height, const Vector3& lightCenter, float lightRadius);
     ANDROID_API void setOpaque(bool opaque);
     ANDROID_API int syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos,
-            float density, int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom);
+            float density);
     ANDROID_API void destroyCanvasAndSurface();
 
     ANDROID_API void invokeFunctor(Functor* functor, bool waitForCompletion);
diff --git a/libs/hwui/utils/Blur.cpp b/libs/hwui/utils/Blur.cpp
index c020b40..877a422 100644
--- a/libs/hwui/utils/Blur.cpp
+++ b/libs/hwui/utils/Blur.cpp
@@ -19,6 +19,7 @@
 #include <math.h>
 
 #include "Blur.h"
+#include "MathUtils.h"
 
 namespace android {
 namespace uirenderer {
@@ -35,6 +36,17 @@
     return sigma > 0.5f ? (sigma - 0.5f) / BLUR_SIGMA_SCALE : 0.0f;
 }
 
+// if the original radius was on an integer boundary and the resulting radius
+// is within the conversion error tolerance then we attempt to snap to the
+// original integer boundary.
+uint32_t Blur::convertRadiusToInt(float radius) {
+    const float radiusCeil  = ceilf(radius);
+    if (MathUtils::areEqual(radiusCeil, radius)) {
+        return radiusCeil;
+    }
+    return radius;
+}
+
 /**
  * HWUI has used a slightly different equation than Skia to generate the value
  * for sigma and to preserve compatibility we have kept that logic.
diff --git a/libs/hwui/utils/Blur.h b/libs/hwui/utils/Blur.h
index 79aff65..b145333 100644
--- a/libs/hwui/utils/Blur.h
+++ b/libs/hwui/utils/Blur.h
@@ -27,8 +27,12 @@
 public:
     // If radius > 0, return the corresponding sigma, else return 0
     ANDROID_API static float convertRadiusToSigma(float radius);
-    // If sigma > 0.6, return the corresponding radius, else return 0
+    // If sigma > 0.5, return the corresponding radius, else return 0
     ANDROID_API static float convertSigmaToRadius(float sigma);
+    // If the original radius was on an integer boundary then after the sigma to
+    // radius conversion a small rounding error may be introduced. This function
+    // accounts for that error and snaps to the appropriate integer boundary.
+    static uint32_t convertRadiusToInt(float radius);
 
     static void generateGaussianWeights(float* weights, int32_t radius);
     static void horizontal(float* weights, int32_t radius, const uint8_t* source,
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 5a3aaab..394e437 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -17,6 +17,8 @@
 package android.media;
 
 import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -26,11 +28,10 @@
 import java.util.Set;
 
 /**
- * @hide
  * A class to encapsulate a collection of attributes describing information about an audio
  * player or recorder.
  */
-public final class AudioAttributes {
+public final class AudioAttributes implements Parcelable {
     private final static String TAG = "AudioAttributes";
 
     /**
@@ -193,6 +194,7 @@
     }
 
     /**
+     * @hide
      * Return the set of tags.
      * @return a read-only set of all tags stored as strings.
      */
@@ -325,6 +327,7 @@
         }
 
         /**
+         * @hide
          * Add a custom tag stored as a string
          * @param tag
          * @return the same Builder instance.
@@ -411,6 +414,49 @@
                 + " tags=" + mTags);
     }
 
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mUsage);
+        dest.writeInt(mContentType);
+        dest.writeInt(mFlags);
+        String[] tagsArray = new String[mTags.size()];
+        mTags.toArray(tagsArray);
+        dest.writeStringArray(tagsArray);
+    }
+
+    private AudioAttributes(Parcel in) {
+        mUsage = in.readInt();
+        mContentType = in.readInt();
+        mFlags = in.readInt();
+        mTags = new HashSet<String>();
+        String[] tagsArray = in.readStringArray();
+        for (int i = tagsArray.length - 1 ; i >= 0 ; i--) {
+            mTags.add(tagsArray[i]);
+        }
+    }
+
+    /** @hide */
+    public static final Parcelable.Creator<AudioAttributes> CREATOR
+            = new Parcelable.Creator<AudioAttributes>() {
+        /**
+         * Rebuilds an AudioAttributes previously stored with writeToParcel().
+         * @param p Parcel object to read the AudioAttributes from
+         * @return a new AudioAttributes created from the data in the parcel
+         */
+        public AudioAttributes createFromParcel(Parcel p) {
+            return new AudioAttributes(p);
+        }
+        public AudioAttributes[] newArray(int size) {
+            return new AudioAttributes[size];
+        }
+    };
+
+
     /** @hide */
     @IntDef({
         USAGE_UNKNOWN,
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 57274ee..4b4be1b 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -16,6 +16,11 @@
 
 package android.media;
 
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * The AudioFormat class is used to access a number of audio format and
  * channel configuration constants. They are for instance used
@@ -156,4 +161,157 @@
         }
     }
 
+    /** @removed */
+    public AudioFormat()
+    {
+        throw new UnsupportedOperationException("There is no valid usage of this constructor");
+    }
+
+    /**
+     * Private constructor with an ignored argument to differentiate from the removed default ctor
+     * @param ignoredArgument
+     */
+    private AudioFormat(int ignoredArgument) {
+    }
+
+    /** @hide */
+    public final static int AUDIO_FORMAT_HAS_PROPERTY_NONE = 0x0;
+    /** @hide */
+    public final static int AUDIO_FORMAT_HAS_PROPERTY_ENCODING = 0x1 << 0;
+    /** @hide */
+    public final static int AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE = 0x1 << 1;
+    /** @hide */
+    public final static int AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK = 0x1 << 2;
+
+    private int mEncoding;
+    private int mSampleRate;
+    private int mChannelMask;
+    private int mPropertySetMask;
+
+    /**
+     * @hide CANDIDATE FOR PUBLIC API
+     * Builder class for {@link AudioFormat} objects.
+     */
+    public static class Builder {
+        private int mEncoding = ENCODING_DEFAULT;
+        private int mSampleRate = 0;
+        private int mChannelMask = CHANNEL_INVALID;
+        private int mPropertySetMask = AUDIO_FORMAT_HAS_PROPERTY_NONE;
+
+        /**
+         * Constructs a new Builder with the defaults.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Constructs a new Builder from a given {@link AudioFormat}.
+         * @param af the {@link AudioFormat} object whose data will be reused in the new Builder.
+         */
+        public Builder(AudioFormat af) {
+            mEncoding = af.mEncoding;
+            mSampleRate = af.mSampleRate;
+            mChannelMask = af.mChannelMask;
+            mPropertySetMask = af.mPropertySetMask;
+        }
+
+        /**
+         * Combines all of the format characteristics that have been set and return a new
+         * {@link AudioFormat} object.
+         * @return a new {@link AudioFormat} object
+         */
+        public AudioFormat build() {
+            AudioFormat af = new AudioFormat(1980/*ignored*/);
+            af.mEncoding = mEncoding;
+            af.mSampleRate = mSampleRate;
+            af.mChannelMask = mChannelMask;
+            af.mPropertySetMask = mPropertySetMask;
+            return af;
+        }
+
+        /**
+         * Sets the data encoding format.
+         * @param encoding one of {@link AudioFormat#ENCODING_DEFAULT},
+         *     {@link AudioFormat#ENCODING_PCM_8BIT},
+         *     {@link AudioFormat#ENCODING_PCM_16BIT},
+         *     {@link AudioFormat#ENCODING_PCM_FLOAT}.
+         * @return the same Builder instance.
+         * @throws java.lang.IllegalArgumentException
+         */
+        public Builder setEncoding(@Encoding int encoding) throws IllegalArgumentException {
+            switch (encoding) {
+                case ENCODING_DEFAULT:
+                    mEncoding = ENCODING_PCM_16BIT;
+                    break;
+                case ENCODING_PCM_8BIT:
+                case ENCODING_PCM_16BIT:
+                case ENCODING_PCM_FLOAT:
+                    mEncoding = encoding;
+                    break;
+                case ENCODING_INVALID:
+                default:
+                    throw new IllegalArgumentException("Invalid encoding " + encoding);
+            }
+            mPropertySetMask |= AUDIO_FORMAT_HAS_PROPERTY_ENCODING;
+            return this;
+        }
+
+        /**
+         * Sets the channel mask.
+         * @param channelMask describes the configuration of the audio channels.
+         *    <p>For output, the mask should be a combination of
+         *    {@link AudioFormat#CHANNEL_OUT_FRONT_LEFT},
+         *    {@link AudioFormat#CHANNEL_OUT_FRONT_CENTER},
+         *    {@link AudioFormat#CHANNEL_OUT_FRONT_RIGHT},
+         *    {@link AudioFormat#CHANNEL_OUT_SIDE_LEFT},
+         *    {@link AudioFormat#CHANNEL_OUT_SIDE_RIGHT},
+         *    {@link AudioFormat#CHANNEL_OUT_BACK_LEFT},
+         *    {@link AudioFormat#CHANNEL_OUT_BACK_RIGHT}.
+         *    <p>for input, the mask should be {@link AudioFormat#CHANNEL_IN_MONO} or
+         *    {@link AudioFormat#CHANNEL_IN_STEREO}.  {@link AudioFormat#CHANNEL_IN_MONO} is
+         *    guaranteed to work on all devices.
+         * @return the same Builder instance.
+         */
+        public Builder setChannelMask(int channelMask) {
+            // only validated when used, with input or output context
+            mChannelMask = channelMask;
+            mPropertySetMask |= AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK;
+            return this;
+        }
+
+        /**
+         * Sets the sample rate.
+         * @param sampleRate the sample rate expressed in Hz
+         * @return the same Builder instance.
+         * @throws java.lang.IllegalArgumentException
+         */
+        public Builder setSampleRate(int sampleRate) throws IllegalArgumentException {
+            if ((sampleRate <= 0) || (sampleRate > 192000)) {
+                throw new IllegalArgumentException("Invalid sample rate " + sampleRate);
+            }
+            mSampleRate = sampleRate;
+            mPropertySetMask |= AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE;
+            return this;
+        }
+    }
+
+    @Override
+    public String toString () {
+        return new String("AudioFormat:"
+                + " props=" + mPropertySetMask
+                + " enc=" + mEncoding
+                + " chan=0x" + Integer.toHexString(mChannelMask)
+                + " rate=" + mSampleRate);
+    }
+
+    /** @hide */
+    @IntDef({
+        ENCODING_DEFAULT,
+        ENCODING_PCM_8BIT,
+        ENCODING_PCM_16BIT,
+        ENCODING_PCM_FLOAT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Encoding {}
+
 }
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 458139b..6280fde 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1017,7 +1017,7 @@
     public void setMasterMute(boolean state, int flags) {
         IAudioService service = getService();
         try {
-            service.setMasterMute(state, flags, mICallBack);
+            service.setMasterMute(state, flags, mContext.getOpPackageName(), mICallBack);
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in setMasterMute", e);
         }
@@ -1653,6 +1653,25 @@
         }
     }
 
+
+    /**
+     * Return a new audio session identifier not associated with any player or effect.
+     * It can for instance be used to create one of the {@link android.media.audiofx.AudioEffect}
+     * objects.
+     * @return a new unclaimed and unused audio session identifier, or {@link #ERROR} when the
+     *   system failed to allocate a new session.
+     */
+    public int allocateAudioSessionId() {
+        int session = AudioSystem.newAudioSessionId();
+        if (session > 0) {
+            return session;
+        } else {
+            Log.e(TAG, "Failure to allocate a new audio session ID");
+            return ERROR;
+        }
+    }
+
+
     /*
      * Sets a generic audio configuration parameter. The use of these parameters
      * are platform dependant, see libaudio
@@ -2846,18 +2865,22 @@
     }
 
      /**
-     * Indicate A2DP sink connection state change.
+     * Indicate A2DP source or sink connection state change.
      * @param device Bluetooth device connected/disconnected
      * @param state  new connection state (BluetoothProfile.STATE_xxx)
+     * @param profile profile for the A2DP device
+     * (either {@link android.bluetooth.BluetoothProfile.A2DP} or
+     * {@link android.bluetooth.BluetoothProfile.A2DP_SINK})
      * @return a delay in ms that the caller should wait before broadcasting
      * BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED intent.
      * {@hide}
      */
-    public int setBluetoothA2dpDeviceConnectionState(BluetoothDevice device, int state) {
+    public int setBluetoothA2dpDeviceConnectionState(BluetoothDevice device, int state,
+            int profile) {
         IAudioService service = getService();
         int delay = 0;
         try {
-            delay = service.setBluetoothA2dpDeviceConnectionState(device, state);
+            delay = service.setBluetoothA2dpDeviceConnectionState(device, state, profile);
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in setBluetoothA2dpDeviceConnectionState "+e);
         } finally {
@@ -3003,7 +3026,8 @@
     /** @hide
      */
     public static final int SUCCESS = AudioSystem.SUCCESS;
-    /** @hide
+    /**
+     * A default error code.
      */
     public static final int ERROR = AudioSystem.ERROR;
     /** @hide
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index fd346d5..2f782cc 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -177,7 +177,8 @@
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
     //   and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
     private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 100;
-    private static final int MSG_SET_A2DP_CONNECTION_STATE = 101;
+    private static final int MSG_SET_A2DP_SRC_CONNECTION_STATE = 101;
+    private static final int MSG_SET_A2DP_SINK_CONNECTION_STATE = 102;
     // end of messages handled under wakelock
 
     private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000;
@@ -1320,11 +1321,16 @@
     }
 
     /** @see AudioManager#setMasterMute(boolean, int) */
-    public void setMasterMute(boolean state, int flags, IBinder cb) {
+    public void setMasterMute(boolean state, int flags, String callingPackage, IBinder cb) {
         if (mUseFixedVolume) {
             return;
         }
 
+        if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, Binder.getCallingUid(),
+                callingPackage) != AppOpsManager.MODE_ALLOWED) {
+            return;
+        }
+
         if (state != AudioSystem.getMasterMute()) {
             AudioSystem.setMasterMute(state);
             // Post a persist master volume msg
@@ -2384,10 +2390,10 @@
                         synchronized (mConnectedDevices) {
                             int state = mA2dp.getConnectionState(btDevice);
                             int delay = checkSendBecomingNoisyIntent(
-                                                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                                                    (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0);
+                                                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                                                (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0);
                             queueMsgUnderWakeLock(mAudioHandler,
-                                    MSG_SET_A2DP_CONNECTION_STATE,
+                                    MSG_SET_A2DP_SINK_CONNECTION_STATE,
                                     state,
                                     0,
                                     btDevice,
@@ -2397,6 +2403,22 @@
                 }
                 break;
 
+            case BluetoothProfile.A2DP_SINK:
+                deviceList = proxy.getConnectedDevices();
+                if (deviceList.size() > 0) {
+                    btDevice = deviceList.get(0);
+                    synchronized (mConnectedDevices) {
+                        int state = proxy.getConnectionState(btDevice);
+                        queueMsgUnderWakeLock(mAudioHandler,
+                                MSG_SET_A2DP_SRC_CONNECTION_STATE,
+                                state,
+                                0,
+                                btDevice,
+                                0 /* delay */);
+                    }
+                }
+                break;
+
             case BluetoothProfile.HEADSET:
                 synchronized (mScoClients) {
                     // Discard timeout message
@@ -2465,6 +2487,15 @@
                 }
                 break;
 
+            case BluetoothProfile.A2DP_SINK:
+                synchronized (mConnectedDevices) {
+                    if (mConnectedDevices.containsKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP)) {
+                        makeA2dpSrcUnavailable(
+                                mConnectedDevices.get(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP));
+                    }
+                }
+                break;
+
             case BluetoothProfile.HEADSET:
                 synchronized (mScoClients) {
                     mBluetoothHeadset = null;
@@ -2880,14 +2911,22 @@
         }
     }
 
-    public int setBluetoothA2dpDeviceConnectionState(BluetoothDevice device, int state)
+    public int setBluetoothA2dpDeviceConnectionState(BluetoothDevice device, int state, int profile)
     {
         int delay;
+        if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
+            throw new IllegalArgumentException("invalid profile " + profile);
+        }
         synchronized (mConnectedDevices) {
-            delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                                            (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0);
+            if (profile == BluetoothProfile.A2DP) {
+                delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                                                (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0);
+            } else {
+                delay = 0;
+            }
             queueMsgUnderWakeLock(mAudioHandler,
-                    MSG_SET_A2DP_CONNECTION_STATE,
+                    (profile == BluetoothProfile.A2DP ?
+                        MSG_SET_A2DP_SINK_CONNECTION_STATE : MSG_SET_A2DP_SRC_CONNECTION_STATE),
                     state,
                     0,
                     device,
@@ -2933,54 +2972,56 @@
             return name + "_" + suffix;
         }
 
-        public synchronized void readSettings() {
-            // force maximum volume on all streams if fixed volume property is set
-            if (mUseFixedVolume) {
-                mIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
-                return;
-            }
-            // do not read system stream volume from settings: this stream is always aliased
-            // to another stream type and its volume is never persisted. Values in settings can
-            // only be stale values
-            if ((mStreamType == AudioSystem.STREAM_SYSTEM) ||
-                    (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) {
-                int index = 10 * AudioManager.DEFAULT_STREAM_VOLUME[mStreamType];
-                synchronized (mCameraSoundForced) {
-                    if (mCameraSoundForced) {
-                        index = mIndexMax;
+        public void readSettings() {
+            synchronized (VolumeStreamState.class) {
+                // force maximum volume on all streams if fixed volume property is set
+                if (mUseFixedVolume) {
+                    mIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
+                    return;
+                }
+                // do not read system stream volume from settings: this stream is always aliased
+                // to another stream type and its volume is never persisted. Values in settings can
+                // only be stale values
+                if ((mStreamType == AudioSystem.STREAM_SYSTEM) ||
+                        (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) {
+                    int index = 10 * AudioManager.DEFAULT_STREAM_VOLUME[mStreamType];
+                    synchronized (mCameraSoundForced) {
+                        if (mCameraSoundForced) {
+                            index = mIndexMax;
+                        }
                     }
-                }
-                mIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
-                return;
-            }
-
-            int remainingDevices = AudioSystem.DEVICE_OUT_ALL;
-
-            for (int i = 0; remainingDevices != 0; i++) {
-                int device = (1 << i);
-                if ((device & remainingDevices) == 0) {
-                    continue;
-                }
-                remainingDevices &= ~device;
-
-                // retrieve current volume for device
-                String name = getSettingNameForDevice(device);
-                // if no volume stored for current stream and device, use default volume if default
-                // device, continue otherwise
-                int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT) ?
-                                        AudioManager.DEFAULT_STREAM_VOLUME[mStreamType] : -1;
-                int index = Settings.System.getIntForUser(
-                        mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
-                if (index == -1) {
-                    continue;
+                    mIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
+                    return;
                 }
 
-                // ignore settings for fixed volume devices: volume should always be at max or 0
-                if ((mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) &&
-                        ((device & mFixedVolumeDevices) != 0)) {
-                    mIndex.put(device, (index != 0) ? mIndexMax : 0);
-                } else {
-                    mIndex.put(device, getValidIndex(10 * index));
+                int remainingDevices = AudioSystem.DEVICE_OUT_ALL;
+
+                for (int i = 0; remainingDevices != 0; i++) {
+                    int device = (1 << i);
+                    if ((device & remainingDevices) == 0) {
+                        continue;
+                    }
+                    remainingDevices &= ~device;
+
+                    // retrieve current volume for device
+                    String name = getSettingNameForDevice(device);
+                    // if no volume stored for current stream and device, use default volume if default
+                    // device, continue otherwise
+                    int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT) ?
+                                            AudioManager.DEFAULT_STREAM_VOLUME[mStreamType] : -1;
+                    int index = Settings.System.getIntForUser(
+                            mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
+                    if (index == -1) {
+                        continue;
+                    }
+
+                    // ignore settings for fixed volume devices: volume should always be at max or 0
+                    if ((mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) &&
+                            ((device & mFixedVolumeDevices) != 0)) {
+                        mIndex.put(device, (index != 0) ? mIndexMax : 0);
+                    } else {
+                        mIndex.put(device, getValidIndex(10 * index));
+                    }
                 }
             }
         }
@@ -2998,32 +3039,34 @@
             AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
         }
 
-        public synchronized void applyAllVolumes() {
-            // apply default volume first: by convention this will reset all
-            // devices volumes in audio policy manager to the supplied value
-            int index;
-            if (isMuted()) {
-                index = 0;
-            } else {
-                index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10;
-            }
-            AudioSystem.setStreamVolumeIndex(mStreamType, index, AudioSystem.DEVICE_OUT_DEFAULT);
-            // then apply device specific volumes
-            Set set = mIndex.entrySet();
-            Iterator i = set.iterator();
-            while (i.hasNext()) {
-                Map.Entry entry = (Map.Entry)i.next();
-                int device = ((Integer)entry.getKey()).intValue();
-                if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
-                    if (isMuted()) {
-                        index = 0;
-                    } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
-                            mAvrcpAbsVolSupported) {
-                        index = (mIndexMax + 5)/10;
-                    } else {
-                        index = ((Integer)entry.getValue() + 5)/10;
+        public void applyAllVolumes() {
+            synchronized (VolumeStreamState.class) {
+                // apply default volume first: by convention this will reset all
+                // devices volumes in audio policy manager to the supplied value
+                int index;
+                if (isMuted()) {
+                    index = 0;
+                } else {
+                    index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10;
+                }
+                AudioSystem.setStreamVolumeIndex(mStreamType, index, AudioSystem.DEVICE_OUT_DEFAULT);
+                // then apply device specific volumes
+                Set set = mIndex.entrySet();
+                Iterator i = set.iterator();
+                while (i.hasNext()) {
+                    Map.Entry entry = (Map.Entry)i.next();
+                    int device = ((Integer)entry.getKey()).intValue();
+                    if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
+                        if (isMuted()) {
+                            index = 0;
+                        } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
+                                mAvrcpAbsVolSupported) {
+                            index = (mIndexMax + 5)/10;
+                        } else {
+                            index = ((Integer)entry.getValue() + 5)/10;
+                        }
+                        AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
                     }
-                    AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
                 }
             }
         }
@@ -3033,94 +3076,104 @@
                             device);
         }
 
-        public synchronized boolean setIndex(int index, int device) {
-            int oldIndex = getIndex(device);
-            index = getValidIndex(index);
-            synchronized (mCameraSoundForced) {
-                if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
-                    index = mIndexMax;
-                }
-            }
-            mIndex.put(device, index);
-
-            if (oldIndex != index) {
-                // Apply change to all streams using this one as alias
-                // if changing volume of current device, also change volume of current
-                // device on aliased stream
-                boolean currentDevice = (device == getDeviceForStream(mStreamType));
-                int numStreamTypes = AudioSystem.getNumStreamTypes();
-                for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
-                    if (streamType != mStreamType &&
-                            mStreamVolumeAlias[streamType] == mStreamType) {
-                        int scaledIndex = rescaleIndex(index, mStreamType, streamType);
-                        mStreamStates[streamType].setIndex(scaledIndex,
-                                                           device);
-                        if (currentDevice) {
-                            mStreamStates[streamType].setIndex(scaledIndex,
-                                                               getDeviceForStream(streamType));
-                        }
+        public boolean setIndex(int index, int device) {
+            synchronized (VolumeStreamState.class) {
+                int oldIndex = getIndex(device);
+                index = getValidIndex(index);
+                synchronized (mCameraSoundForced) {
+                    if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
+                        index = mIndexMax;
                     }
                 }
-                return true;
-            } else {
-                return false;
+                mIndex.put(device, index);
+
+                if (oldIndex != index) {
+                    // Apply change to all streams using this one as alias
+                    // if changing volume of current device, also change volume of current
+                    // device on aliased stream
+                    boolean currentDevice = (device == getDeviceForStream(mStreamType));
+                    int numStreamTypes = AudioSystem.getNumStreamTypes();
+                    for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+                        if (streamType != mStreamType &&
+                                mStreamVolumeAlias[streamType] == mStreamType) {
+                            int scaledIndex = rescaleIndex(index, mStreamType, streamType);
+                            mStreamStates[streamType].setIndex(scaledIndex,
+                                                               device);
+                            if (currentDevice) {
+                                mStreamStates[streamType].setIndex(scaledIndex,
+                                                                   getDeviceForStream(streamType));
+                            }
+                        }
+                    }
+                    return true;
+                } else {
+                    return false;
+                }
             }
         }
 
-        public synchronized int getIndex(int device) {
-            Integer index = mIndex.get(device);
-            if (index == null) {
-                // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
-                index = mIndex.get(AudioSystem.DEVICE_OUT_DEFAULT);
+        public int getIndex(int device) {
+            synchronized (VolumeStreamState.class) {
+                Integer index = mIndex.get(device);
+                if (index == null) {
+                    // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
+                    index = mIndex.get(AudioSystem.DEVICE_OUT_DEFAULT);
+                }
+                return index.intValue();
             }
-            return index.intValue();
         }
 
         public int getMaxIndex() {
             return mIndexMax;
         }
 
-        public synchronized void setAllIndexes(VolumeStreamState srcStream) {
-            int srcStreamType = srcStream.getStreamType();
-            // apply default device volume from source stream to all devices first in case
-            // some devices are present in this stream state but not in source stream state
-            int index = srcStream.getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
-            index = rescaleIndex(index, srcStreamType, mStreamType);
-            Set set = mIndex.entrySet();
-            Iterator i = set.iterator();
-            while (i.hasNext()) {
-                Map.Entry entry = (Map.Entry)i.next();
-                entry.setValue(index);
-            }
-            // Now apply actual volume for devices in source stream state
-            set = srcStream.mIndex.entrySet();
-            i = set.iterator();
-            while (i.hasNext()) {
-                Map.Entry entry = (Map.Entry)i.next();
-                int device = ((Integer)entry.getKey()).intValue();
-                index = ((Integer)entry.getValue()).intValue();
+        public void setAllIndexes(VolumeStreamState srcStream) {
+            synchronized (VolumeStreamState.class) {
+                int srcStreamType = srcStream.getStreamType();
+                // apply default device volume from source stream to all devices first in case
+                // some devices are present in this stream state but not in source stream state
+                int index = srcStream.getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
                 index = rescaleIndex(index, srcStreamType, mStreamType);
+                Set set = mIndex.entrySet();
+                Iterator i = set.iterator();
+                while (i.hasNext()) {
+                    Map.Entry entry = (Map.Entry)i.next();
+                    entry.setValue(index);
+                }
+                // Now apply actual volume for devices in source stream state
+                set = srcStream.mIndex.entrySet();
+                i = set.iterator();
+                while (i.hasNext()) {
+                    Map.Entry entry = (Map.Entry)i.next();
+                    int device = ((Integer)entry.getKey()).intValue();
+                    index = ((Integer)entry.getValue()).intValue();
+                    index = rescaleIndex(index, srcStreamType, mStreamType);
 
-                setIndex(index, device);
+                    setIndex(index, device);
+                }
             }
         }
 
-        public synchronized void setAllIndexesToMax() {
-            Set set = mIndex.entrySet();
-            Iterator i = set.iterator();
-            while (i.hasNext()) {
-                Map.Entry entry = (Map.Entry)i.next();
-                entry.setValue(mIndexMax);
+        public void setAllIndexesToMax() {
+            synchronized (VolumeStreamState.class) {
+                Set set = mIndex.entrySet();
+                Iterator i = set.iterator();
+                while (i.hasNext()) {
+                    Map.Entry entry = (Map.Entry)i.next();
+                    entry.setValue(mIndexMax);
+                }
             }
         }
 
-        public synchronized void mute(IBinder cb, boolean state) {
-            VolumeDeathHandler handler = getDeathHandler(cb, state);
-            if (handler == null) {
-                Log.e(TAG, "Could not get client death handler for stream: "+mStreamType);
-                return;
+        public void mute(IBinder cb, boolean state) {
+            synchronized (VolumeStreamState.class) {
+                VolumeDeathHandler handler = getDeathHandler(cb, state);
+                if (handler == null) {
+                    Log.e(TAG, "Could not get client death handler for stream: "+mStreamType);
+                    return;
+                }
+                handler.mute(state);
             }
-            handler.mute(state);
         }
 
         public int getStreamType() {
@@ -3729,8 +3782,13 @@
                     mAudioEventWakeLock.release();
                     break;
 
-                case MSG_SET_A2DP_CONNECTION_STATE:
-                    onSetA2dpConnectionState((BluetoothDevice)msg.obj, msg.arg1);
+                case MSG_SET_A2DP_SRC_CONNECTION_STATE:
+                    onSetA2dpSourceConnectionState((BluetoothDevice)msg.obj, msg.arg1);
+                    mAudioEventWakeLock.release();
+                    break;
+
+                case MSG_SET_A2DP_SINK_CONNECTION_STATE:
+                    onSetA2dpSinkConnectionState((BluetoothDevice)msg.obj, msg.arg1);
                     mAudioEventWakeLock.release();
                     break;
 
@@ -3857,6 +3915,23 @@
     }
 
     // must be called synchronized on mConnectedDevices
+    private void makeA2dpSrcAvailable(String address) {
+        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
+                AudioSystem.DEVICE_STATE_AVAILABLE,
+                address);
+        mConnectedDevices.put( new Integer(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP),
+                address);
+    }
+
+    // must be called synchronized on mConnectedDevices
+    private void makeA2dpSrcUnavailable(String address) {
+        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
+                AudioSystem.DEVICE_STATE_UNAVAILABLE,
+                address);
+        mConnectedDevices.remove(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP);
+    }
+
+    // must be called synchronized on mConnectedDevices
     private void cancelA2dpDeviceTimeout() {
         mAudioHandler.removeMessages(MSG_BTA2DP_DOCK_TIMEOUT);
     }
@@ -3866,9 +3941,11 @@
         return mAudioHandler.hasMessages(MSG_BTA2DP_DOCK_TIMEOUT);
     }
 
-    private void onSetA2dpConnectionState(BluetoothDevice btDevice, int state)
+    private void onSetA2dpSinkConnectionState(BluetoothDevice btDevice, int state)
     {
-        if (DEBUG_VOL) Log.d(TAG, "onSetA2dpConnectionState btDevice="+btDevice+" state="+state);
+        if (DEBUG_VOL) {
+            Log.d(TAG, "onSetA2dpSinkConnectionState btDevice="+btDevice+"state="+state);
+        }
         if (btDevice == null) {
             return;
         }
@@ -3927,6 +4004,32 @@
         }
     }
 
+    private void onSetA2dpSourceConnectionState(BluetoothDevice btDevice, int state)
+    {
+        if (DEBUG_VOL) {
+            Log.d(TAG, "onSetA2dpSourceConnectionState btDevice="+btDevice+" state="+state);
+        }
+        if (btDevice == null) {
+            return;
+        }
+        String address = btDevice.getAddress();
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            address = "";
+        }
+
+        synchronized (mConnectedDevices) {
+                boolean isConnected =
+                (mConnectedDevices.containsKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) &&
+                 mConnectedDevices.get(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP).equals(address));
+
+            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+                makeA2dpSrcUnavailable(address);
+            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+                makeA2dpSrcAvailable(address);
+            }
+        }
+    }
+
     public void avrcpSupportsAbsoluteVolume(String address, boolean support) {
         // address is not used for now, but may be used when multiple a2dp devices are supported
         synchronized (mA2dpAvrcpLock) {
@@ -3992,7 +4095,8 @@
             }
         }
 
-        if (mAudioHandler.hasMessages(MSG_SET_A2DP_CONNECTION_STATE) ||
+        if (mAudioHandler.hasMessages(MSG_SET_A2DP_SRC_CONNECTION_STATE) ||
+                mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE) ||
                 mAudioHandler.hasMessages(MSG_SET_WIRED_DEVICE_CONNECTION_STATE)) {
             delay = 1000;
         }
@@ -4059,8 +4163,9 @@
                     (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE))) {
                 setBluetoothA2dpOnInt(true);
             }
-            boolean isUsb = ((device & AudioSystem.DEVICE_OUT_ALL_USB) != 0) ||
-                            ((device & AudioSystem.DEVICE_IN_ALL_USB) != 0);
+            boolean isUsb = ((device & ~AudioSystem.DEVICE_OUT_ALL_USB) == 0) ||
+                            (((device & AudioSystem.DEVICE_BIT_IN) != 0) &&
+                             ((device & ~AudioSystem.DEVICE_IN_ALL_USB) == 0));
             handleDeviceConnection((state == 1), device, (isUsb ? name : ""));
             if (state != 0) {
                 if ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) ||
@@ -4077,7 +4182,7 @@
                             MUSIC_ACTIVE_POLL_PERIOD_MS);
                 }
             }
-            if (!isUsb) {
+            if (!isUsb && (device != AudioSystem.DEVICE_IN_WIRED_HEADSET)) {
                 sendDeviceConnectionIntent(device, state, name);
             }
         }
@@ -4093,7 +4198,8 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            int device;
+            int outDevice;
+            int inDevice;
             int state;
 
             if (action.equals(Intent.ACTION_DOCK_EVENT)) {
@@ -4128,7 +4234,8 @@
             } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
                 state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
                                                BluetoothProfile.STATE_DISCONNECTED);
-                device = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
+                outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
+                inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
                 String address = null;
 
                 BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
@@ -4142,10 +4249,10 @@
                     switch (btClass.getDeviceClass()) {
                     case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
                     case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
-                        device = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
+                        outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
                         break;
                     case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
-                        device = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
+                        outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
                         break;
                     }
                 }
@@ -4155,7 +4262,9 @@
                 }
 
                 boolean connected = (state == BluetoothProfile.STATE_CONNECTED);
-                if (handleDeviceConnection(connected, device, address)) {
+                boolean success = handleDeviceConnection(connected, outDevice, address) &&
+                                      handleDeviceConnection(connected, inDevice, address);
+                if (success) {
                     synchronized (mScoClients) {
                         if (connected) {
                             mBluetoothHeadsetDevice = btDevice;
@@ -4175,8 +4284,8 @@
                                     : "card=" + alsaCard + ";device=" + alsaDevice);
 
                 // Playback Device
-                device = AudioSystem.DEVICE_OUT_USB_ACCESSORY;
-                setWiredDeviceConnectionState(device, state, params);
+                outDevice = AudioSystem.DEVICE_OUT_USB_ACCESSORY;
+                setWiredDeviceConnectionState(outDevice, state, params);
             } else if (action.equals(Intent.ACTION_USB_AUDIO_DEVICE_PLUG)) {
                 state = intent.getIntExtra("state", 0);
 
@@ -4191,14 +4300,14 @@
 
                 // Playback Device
                 if (hasPlayback) {
-                    device = AudioSystem.DEVICE_OUT_USB_DEVICE;
-                    setWiredDeviceConnectionState(device, state, params);
+                    outDevice = AudioSystem.DEVICE_OUT_USB_DEVICE;
+                    setWiredDeviceConnectionState(outDevice, state, params);
                 }
 
                 // Capture Device
                 if (hasCapture) {
-                    device = AudioSystem.DEVICE_IN_USB_DEVICE;
-                    setWiredDeviceConnectionState(device, state, params);
+                    inDevice = AudioSystem.DEVICE_IN_USB_DEVICE;
+                    setWiredDeviceConnectionState(inDevice, state, params);
                 }
             } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
                 boolean broadcast = false;
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index af7a3e1..9fbcd18 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -130,6 +130,11 @@
     public static native boolean isSourceActive(int source);
 
     /*
+     * Returns a new unused audio session ID
+     */
+    public static native int newAudioSessionId();
+
+    /*
      * Sets a group generic audio configuration parameters. The use of these parameters
      * are platform dependent, see libaudio
      *
@@ -300,7 +305,7 @@
     public static final int DEVICE_IN_TV_TUNER = DEVICE_BIT_IN | 0x4000;
     public static final int DEVICE_IN_LINE = DEVICE_BIT_IN | 0x8000;
     public static final int DEVICE_IN_SPDIF = DEVICE_BIT_IN | 0x10000;
-
+    public static final int DEVICE_IN_BLUETOOTH_A2DP = DEVICE_BIT_IN | 0x20000;
     public static final int DEVICE_IN_DEFAULT = DEVICE_BIT_IN | DEVICE_BIT_DEFAULT;
 
     public static final int DEVICE_IN_ALL = (DEVICE_IN_COMMUNICATION |
@@ -320,6 +325,7 @@
                                              DEVICE_IN_TV_TUNER |
                                              DEVICE_IN_LINE |
                                              DEVICE_IN_SPDIF |
+                                             DEVICE_IN_BLUETOOTH_A2DP |
                                              DEVICE_IN_DEFAULT);
     public static final int DEVICE_IN_ALL_SCO = DEVICE_IN_BLUETOOTH_SCO_HEADSET;
     public static final int DEVICE_IN_ALL_USB = (DEVICE_IN_USB_ACCESSORY |
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 8eb83e4..cfd9c3b 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -816,6 +816,8 @@
     *         with the estimated time when that frame was presented or is committed to
     *         be presented.
     *         In the case that no timestamp is available, any supplied instance is left unaltered.
+    *         A timestamp may be temporarily unavailable while the audio clock is stabilizing,
+    *         or during and immediately after a route change.
     */
     // Add this text when the "on new timestamp" API is added:
     //   Use if you need to get the most recent timestamp outside of the event callback handler.
diff --git a/media/java/android/media/CamcorderProfile.java b/media/java/android/media/CamcorderProfile.java
index 511111c..f9e49c1 100644
--- a/media/java/android/media/CamcorderProfile.java
+++ b/media/java/android/media/CamcorderProfile.java
@@ -90,9 +90,14 @@
      */
     public static final int QUALITY_QVGA = 7;
 
+    /**
+     * Quality level corresponding to the 2160p (3840x2160) resolution.
+     */
+    public static final int QUALITY_2160P = 8;
+
     // Start and end of quality list
     private static final int QUALITY_LIST_START = QUALITY_LOW;
-    private static final int QUALITY_LIST_END = QUALITY_QVGA;
+    private static final int QUALITY_LIST_END = QUALITY_2160P;
 
     /**
      * Time lapse quality level corresponding to the lowest available resolution.
@@ -134,9 +139,14 @@
      */
     public static final int QUALITY_TIME_LAPSE_QVGA = 1007;
 
+    /**
+     * Time lapse quality level corresponding to the 2160p (3840 x 2160) resolution.
+     */
+    public static final int QUALITY_TIME_LAPSE_2160P = 1008;
+
     // Start and end of timelapse quality list
     private static final int QUALITY_TIME_LAPSE_LIST_START = QUALITY_TIME_LAPSE_LOW;
-    private static final int QUALITY_TIME_LAPSE_LIST_END = QUALITY_TIME_LAPSE_QVGA;
+    private static final int QUALITY_TIME_LAPSE_LIST_END = QUALITY_TIME_LAPSE_2160P;
 
     /**
      * Default recording duration in seconds before the session is terminated.
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 30de4f9..ba3cfb6 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -62,7 +62,7 @@
 
     boolean isStreamMute(int streamType);
 
-    void setMasterMute(boolean state, int flags, IBinder cb);
+    void setMasterMute(boolean state, int flags, String callingPackage, IBinder cb);
 
     boolean isMasterMute();
 
@@ -232,7 +232,7 @@
     int getMasterStreamType();
 
     void setWiredDeviceConnectionState(int device, int state, String name);
-    int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state);
+    int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state, int profile);
 
     AudioRoutesInfo startWatchingRoutes(in IAudioRoutesObserver observer);
 
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 52045d3..7ff2c2e 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -272,6 +272,15 @@
         /** A generic channel type. */
         public static final int TYPE_OTHER = 0x0;
 
+        /** The channel type for NTSC. */
+        public static final int TYPE_NTSC = 0x1;
+
+        /** The channel type for PAL. */
+        public static final int TYPE_PAL = 0x2;
+
+        /** The channel type for SECAM. */
+        public static final int TYPE_SECAM = 0x3;
+
         /** The special channel type used for pass-through inputs such as HDMI. */
         public static final int TYPE_PASSTHROUGH = 0x00010000;
 
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 868c5bf..7b8f2ec 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -26,6 +26,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -223,6 +224,18 @@
         return mService.loadLabel(pm);
     }
 
+    /**
+     * Loads the user-displayed icon for this TV input service.
+     *
+     * @param pm Supplies a PackageManager used to load the TV input's resources.
+     * @return a Drawable containing the TV input's icon. If the TV input does not have
+     *         an icon, application icon is returned. If it's unavailable too, system default is
+     *         returned.
+     */
+    public Drawable loadIcon(PackageManager pm) {
+        return mService.serviceInfo.loadIcon(pm);
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 157dd49..fbe5340 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -24,6 +24,7 @@
 #include <SkBitmap.h>
 #include <media/IMediaHTTPService.h>
 #include <media/mediametadataretriever.h>
+#include <media/mediascanner.h>
 #include <private/media/VideoFrame.h>
 
 #include "jni.h"
@@ -337,17 +338,13 @@
         return NULL;
     }
 
-    unsigned int len = mediaAlbumArt->mSize;
-    char* data = (char*) mediaAlbumArt + sizeof(MediaAlbumArt);
-    jbyteArray array = env->NewByteArray(len);
+    jbyteArray array = env->NewByteArray(mediaAlbumArt->size());
     if (!array) {  // OutOfMemoryError exception has already been thrown.
         ALOGE("getEmbeddedPicture: OutOfMemoryError is thrown.");
     } else {
-        jbyte* bytes = env->GetByteArrayElements(array, NULL);
-        if (bytes != NULL) {
-            memcpy(bytes, data, len);
-            env->ReleaseByteArrayElements(array, bytes, 0);
-        }
+        const jbyte* data =
+                reinterpret_cast<const jbyte*>(mediaAlbumArt->data());
+        env->SetByteArrayRegion(array, 0, mediaAlbumArt->size(), data);
     }
 
     // No need to delete mediaAlbumArt here
diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp
index d21b442..321c2e3 100644
--- a/media/jni/android_media_MediaScanner.cpp
+++ b/media/jni/android_media_MediaScanner.cpp
@@ -21,6 +21,7 @@
 #include <utils/threads.h>
 #include <media/mediascanner.h>
 #include <media/stagefright/StagefrightMediaScanner.h>
+#include <private/media/VideoFrame.h>
 
 #include "jni.h"
 #include "JNIHelp.h"
@@ -347,21 +348,20 @@
     }
 
     int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
-    char* data = mp->extractAlbumArt(fd);
-    if (!data) {
+    MediaAlbumArt* mediaAlbumArt = mp->extractAlbumArt(fd);
+    if (mediaAlbumArt == NULL) {
         return NULL;
     }
-    jsize len = *((uint32_t*)data);
 
-    jbyteArray array = env->NewByteArray(len);
+    jbyteArray array = env->NewByteArray(mediaAlbumArt->size());
     if (array != NULL) {
-        jbyte* bytes = env->GetByteArrayElements(array, NULL);
-        memcpy(bytes, data + 4, len);
-        env->ReleaseByteArrayElements(array, bytes, 0);
+        const jbyte* data =
+                reinterpret_cast<const jbyte*>(mediaAlbumArt->data());
+        env->SetByteArrayRegion(array, 0, mediaAlbumArt->size(), data);
     }
 
 done:
-    free(data);
+    free(mediaAlbumArt);
     // if NewByteArray() returned NULL, an out-of-memory
     // exception will have been raised. I just want to
     // return null in that case.
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index d781336..19b54a6 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -433,16 +433,14 @@
             case MTP_TYPE_STR:
             {
                 jstring stringValue = (jstring)env->GetObjectArrayElement(stringValuesArray, 0);
+                const char* str = (stringValue ? env->GetStringUTFChars(stringValue, NULL) : NULL);
                 if (stringValue) {
-                    const char* str = env->GetStringUTFChars(stringValue, NULL);
-                    if (str == NULL) {
-                        return MTP_RESPONSE_GENERAL_ERROR;
-                    }
                     packet.putString(str);
                     env->ReleaseStringUTFChars(stringValue, str);
                 } else {
                     packet.putEmptyString();
                 }
+                env->DeleteLocalRef(stringValue);
                 break;
              }
             default:
@@ -515,7 +513,7 @@
             break;
          }
         default:
-            ALOGE("unsupported type in getObjectPropertyValue\n");
+            ALOGE("unsupported type in setObjectPropertyValue\n");
             return MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT;
     }
 
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index 36c1d5c..ec87c6e 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -88,7 +88,7 @@
     private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
         /**
          * Creates a new container and copies resource there.
-         * @param paackageURI the uri of resource to be copied. Can be either
+         * @param packageURI the uri of resource to be copied. Can be either
          * a content uri or a file uri
          * @param cid the id of the secure container that should
          * be used for creating a secure container into which the resource
@@ -101,13 +101,13 @@
          */
         public String copyResourceToContainer(final Uri packageURI, final String cid,
                 final String key, final String resFileName, final String publicResFileName,
-                boolean isExternal, boolean isForwardLocked) {
+                boolean isExternal, boolean isForwardLocked, String abiOverride) {
             if (packageURI == null || cid == null) {
                 return null;
             }
 
             return copyResourceInner(packageURI, cid, key, resFileName, publicResFileName,
-                    isExternal, isForwardLocked);
+                    isExternal, isForwardLocked, abiOverride);
         }
 
         /**
@@ -153,13 +153,12 @@
         /**
          * Determine the recommended install location for package
          * specified by file uri location.
-         * @param fileUri the uri of resource to be copied. Should be a
-         * file uri
+         *
          * @return Returns PackageInfoLite object containing
          * the package info and recommended app location.
          */
         public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags,
-                long threshold) {
+                long threshold, String abiOverride) {
             PackageInfoLite ret = new PackageInfoLite();
 
             if (packagePath == null) {
@@ -191,7 +190,7 @@
             ret.verifiers = pkg.verifiers;
 
             ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,
-                    packagePath, flags, threshold);
+                    packagePath, flags, threshold, abiOverride);
 
             return ret;
         }
@@ -208,11 +207,11 @@
         }
 
         @Override
-        public boolean checkExternalFreeStorage(Uri packageUri, boolean isForwardLocked)
-                throws RemoteException {
+        public boolean checkExternalFreeStorage(Uri packageUri, boolean isForwardLocked,
+                String abiOverride) throws RemoteException {
             final File apkFile = new File(packageUri.getPath());
             try {
-                return isUnderExternalThreshold(apkFile, isForwardLocked);
+                return isUnderExternalThreshold(apkFile, isForwardLocked, abiOverride);
             } catch (IOException e) {
                 return true;
             }
@@ -265,11 +264,11 @@
         }
 
         @Override
-        public long calculateInstalledSize(String packagePath, boolean isForwardLocked)
-                throws RemoteException {
+        public long calculateInstalledSize(String packagePath, boolean isForwardLocked,
+                String abiOverride) throws RemoteException {
             final File packageFile = new File(packagePath);
             try {
-                return calculateContainerSize(packageFile, isForwardLocked) * 1024 * 1024;
+                return calculateContainerSize(packageFile, isForwardLocked, abiOverride) * 1024 * 1024;
             } catch (IOException e) {
                 /*
                  * Okay, something failed, so let's just estimate it to be 2x
@@ -328,7 +327,8 @@
     }
 
     private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName,
-            String publicResFileName, boolean isExternal, boolean isForwardLocked) {
+            String publicResFileName, boolean isExternal, boolean isForwardLocked,
+            String abiOverride) {
 
         if (isExternal) {
             // Make sure the sdcard is mounted.
@@ -343,7 +343,22 @@
         String codePath = packageURI.getPath();
         File codeFile = new File(codePath);
         NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(codePath);
-        final int abi = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_ABIS);
+        String[] abiList = Build.SUPPORTED_ABIS;
+        if (abiOverride != null) {
+            abiList = new String[] { abiOverride };
+        } else {
+            try {
+                if (Build.SUPPORTED_64_BIT_ABIS.length > 0 &&
+                        NativeLibraryHelper.hasRenderscriptBitcode(handle)) {
+                    abiList = Build.SUPPORTED_32_BIT_ABIS;
+                }
+            } catch (IOException ioe) {
+                Slog.w(TAG, "Problem determining ABI for: " + codeFile.getPath());
+                return null;
+            }
+        }
+
+        final int abi = NativeLibraryHelper.findSupportedAbi(handle, abiList);
 
         // Calculate size of container needed to hold base APK.
         final int sizeMb;
@@ -414,7 +429,7 @@
             int ret = PackageManager.INSTALL_SUCCEEDED;
             if (abi >= 0) {
                 ret = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle,
-                        sharedLibraryDir, Build.SUPPORTED_ABIS[abi]);
+                        sharedLibraryDir, abiList[abi]);
             } else if (abi != PackageManager.NO_NATIVE_LIBRARIES) {
                 ret = abi;
             }
@@ -672,7 +687,7 @@
     private static final int PREFER_EXTERNAL = 2;
 
     private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags,
-            long threshold) {
+            long threshold, String abiOverride) {
         int prefer;
         boolean checkBoth = false;
 
@@ -741,7 +756,7 @@
         boolean fitsOnSd = false;
         if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) {
             try {
-                fitsOnSd = isUnderExternalThreshold(apkFile, isForwardLocked);
+                fitsOnSd = isUnderExternalThreshold(apkFile, isForwardLocked, abiOverride);
             } catch (IOException e) {
                 return PackageHelper.RECOMMEND_FAILED_INVALID_URI;
             }
@@ -812,13 +827,13 @@
      * @return true if file fits
      * @throws IOException when file does not exist
      */
-    private boolean isUnderExternalThreshold(File apkFile, boolean isForwardLocked)
+    private boolean isUnderExternalThreshold(File apkFile, boolean isForwardLocked, String abiOverride)
             throws IOException {
         if (Environment.isExternalStorageEmulated()) {
             return false;
         }
 
-        final int sizeMb = calculateContainerSize(apkFile, isForwardLocked);
+        final int sizeMb = calculateContainerSize(apkFile, isForwardLocked, abiOverride);
 
         final int availSdMb;
         if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
@@ -832,9 +847,11 @@
         return availSdMb > sizeMb;
     }
 
-    private int calculateContainerSize(File apkFile, boolean forwardLocked) throws IOException {
+    private int calculateContainerSize(File apkFile, boolean forwardLocked,
+            String abiOverride) throws IOException {
         NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(apkFile);
-        final int abi = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_ABIS);
+        final int abi = NativeLibraryHelper.findSupportedAbi(handle,
+                (abiOverride != null) ? new String[] { abiOverride } : Build.SUPPORTED_ABIS);
 
         try {
             return calculateContainerSize(handle, apkFile, abi, forwardLocked);
diff --git a/packages/DocumentsUI/res/values-el/strings.xml b/packages/DocumentsUI/res/values-el/strings.xml
index f0f7e10..24d66ce 100644
--- a/packages/DocumentsUI/res/values-el/strings.xml
+++ b/packages/DocumentsUI/res/values-el/strings.xml
@@ -27,7 +27,7 @@
     <string name="menu_settings" msgid="6008033148948428823">"Ρυθμίσεις"</string>
     <string name="menu_open" msgid="432922957274920903">"Άνοιγμα"</string>
     <string name="menu_save" msgid="2394743337684426338">"Αποθήκευση"</string>
-    <string name="menu_share" msgid="3075149983979628146">"Κοινοποίηση"</string>
+    <string name="menu_share" msgid="3075149983979628146">"Κοινή χρήση"</string>
     <string name="menu_delete" msgid="8138799623850614177">"Διαγραφή"</string>
     <string name="menu_select" msgid="8711270657353563424">"Επιλογή \"<xliff:g id="DIRECTORY">^1</xliff:g>\""</string>
     <string name="mode_selected_count" msgid="459111894725594625">"Επιλέχθηκαν <xliff:g id="COUNT">%1$d</xliff:g>"</string>
@@ -51,5 +51,5 @@
     <string name="empty" msgid="7858882803708117596">"Δεν υπάρχουν στοιχεία"</string>
     <string name="toast_no_application" msgid="1339885974067891667">"Δεν είναι δυνατό το άνοιγμα του αρχείου"</string>
     <string name="toast_failed_delete" msgid="2180678019407244069">"Δεν είναι δυνατή η διαγραφή ορισμένων εγγράφων"</string>
-    <string name="share_via" msgid="8966594246261344259">"Κοινοποίηση μέσω"</string>
+    <string name="share_via" msgid="8966594246261344259">"Κοινή χρήση μέσω"</string>
 </resources>
diff --git a/packages/DocumentsUI/res/values-fi/strings.xml b/packages/DocumentsUI/res/values-fi/strings.xml
index aa118ed..ae04e32 100644
--- a/packages/DocumentsUI/res/values-fi/strings.xml
+++ b/packages/DocumentsUI/res/values-fi/strings.xml
@@ -17,7 +17,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="2783841764617238354">"Asiakirjat"</string>
-    <string name="title_open" msgid="4353228937663917801">"Avoinna alkaen"</string>
+    <string name="title_open" msgid="4353228937663917801">"Avaa sijainnista"</string>
     <string name="title_save" msgid="2433679664882857999">"Tallenna kohteeseen"</string>
     <string name="menu_create_dir" msgid="5947289605844398389">"Luo kansio"</string>
     <string name="menu_grid" msgid="6878021334497835259">"Ruudukkonäkymä"</string>
diff --git a/packages/DocumentsUI/res/values-fr/strings.xml b/packages/DocumentsUI/res/values-fr/strings.xml
index 070b130..b85b518 100644
--- a/packages/DocumentsUI/res/values-fr/strings.xml
+++ b/packages/DocumentsUI/res/values-fr/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" msgid="2783841764617238354">"Documents"</string>
+    <string name="app_label" msgid="2783841764617238354">"Docs"</string>
     <string name="title_open" msgid="4353228937663917801">"Ouvrir à partir de"</string>
     <string name="title_save" msgid="2433679664882857999">"Enregistrer sous"</string>
     <string name="menu_create_dir" msgid="5947289605844398389">"Créer un dossier"</string>
diff --git a/packages/InputDevices/res/values-af/strings.xml b/packages/InputDevices/res/values-af/strings.xml
index 45d1e2a..d67a9fd 100644
--- a/packages/InputDevices/res/values-af/strings.xml
+++ b/packages/InputDevices/res/values-af/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hebreeus"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Litaus"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Spaans (Latyn)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Letties"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-am/strings.xml b/packages/InputDevices/res/values-am/strings.xml
index 3f62df6..3e84794 100644
--- a/packages/InputDevices/res/values-am/strings.xml
+++ b/packages/InputDevices/res/values-am/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"ዕብራስጥ"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"ሊቱዌኒያኛ"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"ስፓኒሽ (ላቲን)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"ላትቪያኛ"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-ar/strings.xml b/packages/InputDevices/res/values-ar/strings.xml
index e70cad4..a922a46 100644
--- a/packages/InputDevices/res/values-ar/strings.xml
+++ b/packages/InputDevices/res/values-ar/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"العبرية"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"الليتوانية"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"الإسبانية (اللاتينية)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"اللاتفية"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-bg/strings.xml b/packages/InputDevices/res/values-bg/strings.xml
index 7582a69..d68a347 100644
--- a/packages/InputDevices/res/values-bg/strings.xml
+++ b/packages/InputDevices/res/values-bg/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Ивритска клавиатурна подредба"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Литовска клавиатурна подредба"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Исп. клав. подредба (Лат. Америка)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"латвийски"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-ca/strings.xml b/packages/InputDevices/res/values-ca/strings.xml
index e38b9a8..6baa5b8 100644
--- a/packages/InputDevices/res/values-ca/strings.xml
+++ b/packages/InputDevices/res/values-ca/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hebreu"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Lituà"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Espanyol (llatí)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Letó"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-cs/strings.xml b/packages/InputDevices/res/values-cs/strings.xml
index 532f3c0..1c502fe 100644
--- a/packages/InputDevices/res/values-cs/strings.xml
+++ b/packages/InputDevices/res/values-cs/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"hebrejština"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"litevština"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"španělština (Latinská Amerika)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Lotyšská klávesnice"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-da/strings.xml b/packages/InputDevices/res/values-da/strings.xml
index 3c907f8..043a5b3 100644
--- a/packages/InputDevices/res/values-da/strings.xml
+++ b/packages/InputDevices/res/values-da/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hebræisk"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Litauisk"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Spansk (latinamerika)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Lettisk"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-de/strings.xml b/packages/InputDevices/res/values-de/strings.xml
index 83be031..04c19e3 100644
--- a/packages/InputDevices/res/values-de/strings.xml
+++ b/packages/InputDevices/res/values-de/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hebräisch"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Litauisch"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Spanisch (Lateinisch)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Lettisch"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-el/strings.xml b/packages/InputDevices/res/values-el/strings.xml
index 99f77ed..025a288 100644
--- a/packages/InputDevices/res/values-el/strings.xml
+++ b/packages/InputDevices/res/values-el/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Εβραϊκά"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Λιθουανικά"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Ισπανικά (Λατινικής Αμερικής)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Λετονικά"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-en-rGB/strings.xml b/packages/InputDevices/res/values-en-rGB/strings.xml
index e22c675..d5797a0 100644
--- a/packages/InputDevices/res/values-en-rGB/strings.xml
+++ b/packages/InputDevices/res/values-en-rGB/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hebrew"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Lithuanian"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Spanish (Latin)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Latvian"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-en-rIN/strings.xml b/packages/InputDevices/res/values-en-rIN/strings.xml
index e22c675..d5797a0 100644
--- a/packages/InputDevices/res/values-en-rIN/strings.xml
+++ b/packages/InputDevices/res/values-en-rIN/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hebrew"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Lithuanian"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Spanish (Latin)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Latvian"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-es-rUS/strings.xml b/packages/InputDevices/res/values-es-rUS/strings.xml
index 5190fc5..0a9d2f3 100644
--- a/packages/InputDevices/res/values-es-rUS/strings.xml
+++ b/packages/InputDevices/res/values-es-rUS/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hebreo"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Lituano"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Español (latino)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Letón"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-es/strings.xml b/packages/InputDevices/res/values-es/strings.xml
index a81ed29..6e41abf 100644
--- a/packages/InputDevices/res/values-es/strings.xml
+++ b/packages/InputDevices/res/values-es/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hebreo"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Lituano"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Español (Latinoamérica)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Letón"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-et-rEE/strings.xml b/packages/InputDevices/res/values-et-rEE/strings.xml
index b90f825..0d931ce 100644
--- a/packages/InputDevices/res/values-et-rEE/strings.xml
+++ b/packages/InputDevices/res/values-et-rEE/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Heebrea"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Leedu"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Hispaania (Ladina-Ameerika)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"läti keel"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-fa/strings.xml b/packages/InputDevices/res/values-fa/strings.xml
index 490b5c2..e87fbad 100644
--- a/packages/InputDevices/res/values-fa/strings.xml
+++ b/packages/InputDevices/res/values-fa/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"عبری"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"لیتوانیایی"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"اسپانیایی (لاتین)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"لتونیایی"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-fi/strings.xml b/packages/InputDevices/res/values-fi/strings.xml
index 060d0e7..5b39dfd 100644
--- a/packages/InputDevices/res/values-fi/strings.xml
+++ b/packages/InputDevices/res/values-fi/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"heprea"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"liettua"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"espanja (Latinalainen Amerikka)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"latvialainen"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-fr-rCA/strings.xml b/packages/InputDevices/res/values-fr-rCA/strings.xml
index 8fc9f79..9973918 100644
--- a/packages/InputDevices/res/values-fr-rCA/strings.xml
+++ b/packages/InputDevices/res/values-fr-rCA/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hébreu"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Lituanien"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Espagnol (latin)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Letton"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-fr/strings.xml b/packages/InputDevices/res/values-fr/strings.xml
index b029b02..fa2977b 100644
--- a/packages/InputDevices/res/values-fr/strings.xml
+++ b/packages/InputDevices/res/values-fr/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hébreu"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Lituanien"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Espagnol (latin)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Letton"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-hi/strings.xml b/packages/InputDevices/res/values-hi/strings.xml
index 8398c92..77cb8fe 100644
--- a/packages/InputDevices/res/values-hi/strings.xml
+++ b/packages/InputDevices/res/values-hi/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"हिब्रू"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"लिथुआनियाई"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"स्पेनिश (लैटिन)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"लातवियाई"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-hr/strings.xml b/packages/InputDevices/res/values-hr/strings.xml
index 68c868f..bad973d 100644
--- a/packages/InputDevices/res/values-hr/strings.xml
+++ b/packages/InputDevices/res/values-hr/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"hebrejski"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"litavski"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"španjolski (Latinska Amerika)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"latvijska"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-hu/strings.xml b/packages/InputDevices/res/values-hu/strings.xml
index af6a571..510591d 100644
--- a/packages/InputDevices/res/values-hu/strings.xml
+++ b/packages/InputDevices/res/values-hu/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"héber"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"litván"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"spanyol (latin-amerikai)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"lett"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-hy-rAM/strings.xml b/packages/InputDevices/res/values-hy-rAM/strings.xml
index 068e559..9ffa8bb 100644
--- a/packages/InputDevices/res/values-hy-rAM/strings.xml
+++ b/packages/InputDevices/res/values-hy-rAM/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Եբրայերեն"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Լիտվերեն"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Իսպաներեն (Լատինական)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"լատիշերեն"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-in/strings.xml b/packages/InputDevices/res/values-in/strings.xml
index d32ac98..fccfa67 100644
--- a/packages/InputDevices/res/values-in/strings.xml
+++ b/packages/InputDevices/res/values-in/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Ibrani"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Lithuania"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Spanyol (Latin)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Latvi"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-it/strings.xml b/packages/InputDevices/res/values-it/strings.xml
index 280a23c..83dba70 100644
--- a/packages/InputDevices/res/values-it/strings.xml
+++ b/packages/InputDevices/res/values-it/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Ebraico"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Lituano"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Spagnolo (America Latina)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Lettone"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-iw/strings.xml b/packages/InputDevices/res/values-iw/strings.xml
index 9bb66d8..26fe662 100644
--- a/packages/InputDevices/res/values-iw/strings.xml
+++ b/packages/InputDevices/res/values-iw/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"עברית"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"ליטאית"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"ספרדית (לטינית)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"לטבית"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-ja/strings.xml b/packages/InputDevices/res/values-ja/strings.xml
index 26b1094..e2b154d 100644
--- a/packages/InputDevices/res/values-ja/strings.xml
+++ b/packages/InputDevices/res/values-ja/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"ヘブライ語"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"リトアニア語"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"スペイン語(中南米)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"ラトビア語"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-ka-rGE/strings.xml b/packages/InputDevices/res/values-ka-rGE/strings.xml
index 35085a1..eff4b04 100644
--- a/packages/InputDevices/res/values-ka-rGE/strings.xml
+++ b/packages/InputDevices/res/values-ka-rGE/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"ებრაული"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"ლიტვური"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"ესპანური (ლათინური)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"ლატვიური"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-km-rKH/strings.xml b/packages/InputDevices/res/values-km-rKH/strings.xml
index ea3b755..60a28b1 100644
--- a/packages/InputDevices/res/values-km-rKH/strings.xml
+++ b/packages/InputDevices/res/values-km-rKH/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"អ៊ីស្រាអែល"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"លីទុយអានី"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"អេស្ប៉ាញ (ឡាតាំង​)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"ឡាតវីយ៉ា"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-ko/strings.xml b/packages/InputDevices/res/values-ko/strings.xml
index e360ed0..3f563d1 100644
--- a/packages/InputDevices/res/values-ko/strings.xml
+++ b/packages/InputDevices/res/values-ko/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"히브리어"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"리투아니아어"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"스페인어(라틴)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"라트비아어"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-lo-rLA/strings.xml b/packages/InputDevices/res/values-lo-rLA/strings.xml
index fc37501..fb3fe17 100644
--- a/packages/InputDevices/res/values-lo-rLA/strings.xml
+++ b/packages/InputDevices/res/values-lo-rLA/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"ຮີບຣິວ"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"​ລິ​ທົວ​ນຽນ"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"​ສະ​ແປນ​ນິດ (ລາ​ຕິນ)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"​ລັດ​ວຽນ"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-lt/strings.xml b/packages/InputDevices/res/values-lt/strings.xml
index c197389..d0eb1f6 100644
--- a/packages/InputDevices/res/values-lt/strings.xml
+++ b/packages/InputDevices/res/values-lt/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hebrajų"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Lietuvių"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Ispanų (Lotynų Amerika)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Latvių k."</string>
 </resources>
diff --git a/packages/InputDevices/res/values-lv/strings.xml b/packages/InputDevices/res/values-lv/strings.xml
index 53d2467..0608bf0 100644
--- a/packages/InputDevices/res/values-lv/strings.xml
+++ b/packages/InputDevices/res/values-lv/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Ivrits"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Lietuviešu"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Spāņu (latīņu)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Latviešu"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-mn-rMN/strings.xml b/packages/InputDevices/res/values-mn-rMN/strings.xml
index 194c577..a28fd2a 100644
--- a/packages/InputDevices/res/values-mn-rMN/strings.xml
+++ b/packages/InputDevices/res/values-mn-rMN/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Еврей"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Литви"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Испани (Латин)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Латви"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-ms-rMY/strings.xml b/packages/InputDevices/res/values-ms-rMY/strings.xml
index a3098c5..a1a6d00 100644
--- a/packages/InputDevices/res/values-ms-rMY/strings.xml
+++ b/packages/InputDevices/res/values-ms-rMY/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Bahasa Ibrani"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Bahasa Lithuania"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Bahasa Sepanyol (Latin)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Bahasa Latvia"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-nb/strings.xml b/packages/InputDevices/res/values-nb/strings.xml
index 47cff5c..ad4b704 100644
--- a/packages/InputDevices/res/values-nb/strings.xml
+++ b/packages/InputDevices/res/values-nb/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hebraisk"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Litauisk"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Spansk (latinsk)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Latvisk"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-nl/strings.xml b/packages/InputDevices/res/values-nl/strings.xml
index e858c9c..c57251e 100644
--- a/packages/InputDevices/res/values-nl/strings.xml
+++ b/packages/InputDevices/res/values-nl/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hebreeuws"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Litouws"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Spaans (Latijns-Amerika)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Lets"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-pl/strings.xml b/packages/InputDevices/res/values-pl/strings.xml
index 0ca91ca..39fb3ec 100644
--- a/packages/InputDevices/res/values-pl/strings.xml
+++ b/packages/InputDevices/res/values-pl/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"hebrajski"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"litewski"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"hiszpański (Ameryka Łacińska)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"łotewski"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-pt-rPT/strings.xml b/packages/InputDevices/res/values-pt-rPT/strings.xml
index d5afe05..3ac3b84 100644
--- a/packages/InputDevices/res/values-pt-rPT/strings.xml
+++ b/packages/InputDevices/res/values-pt-rPT/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hebraico"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Lituano"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Espanhol (América Latina)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Letão"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-pt/strings.xml b/packages/InputDevices/res/values-pt/strings.xml
index 84cf865..e9a0a38 100644
--- a/packages/InputDevices/res/values-pt/strings.xml
+++ b/packages/InputDevices/res/values-pt/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hebraico"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Lituano"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Espanhol (América Latina)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Letão"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-ro/strings.xml b/packages/InputDevices/res/values-ro/strings.xml
index 5278643..c2392b1 100644
--- a/packages/InputDevices/res/values-ro/strings.xml
+++ b/packages/InputDevices/res/values-ro/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Ebraică"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Lituaniană"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Spaniolă (America Latină)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Letonă"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-ru/strings.xml b/packages/InputDevices/res/values-ru/strings.xml
index 3ae2e53..70ecf6e 100644
--- a/packages/InputDevices/res/values-ru/strings.xml
+++ b/packages/InputDevices/res/values-ru/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Иврит"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Литовский"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Испанский (Латинская Америка)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"латышский"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-sk/strings.xml b/packages/InputDevices/res/values-sk/strings.xml
index 1e51167..d2ee0cf 100644
--- a/packages/InputDevices/res/values-sk/strings.xml
+++ b/packages/InputDevices/res/values-sk/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hebrejčina"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Litovčina"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Španielčina (Latinská Amerika)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Lotyština"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-sl/strings.xml b/packages/InputDevices/res/values-sl/strings.xml
index 66e340e..38542ef 100644
--- a/packages/InputDevices/res/values-sl/strings.xml
+++ b/packages/InputDevices/res/values-sl/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"hebrejščina"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"litovščina"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"španščina (Latinska Amerika)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"latvijščina"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-sr/strings.xml b/packages/InputDevices/res/values-sr/strings.xml
index 14ccbf3..dd500e6 100644
--- a/packages/InputDevices/res/values-sr/strings.xml
+++ b/packages/InputDevices/res/values-sr/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"хебрејски"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"литвански"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"шпански (Латинска Америка)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"летонски"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-sv/strings.xml b/packages/InputDevices/res/values-sv/strings.xml
index f3338c6..c2406c0 100644
--- a/packages/InputDevices/res/values-sv/strings.xml
+++ b/packages/InputDevices/res/values-sv/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hebreiska"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Litauiska"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Spanska (latinamerikansk)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"lettiska"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-sw/strings.xml b/packages/InputDevices/res/values-sw/strings.xml
index 336cc33..f71a696 100644
--- a/packages/InputDevices/res/values-sw/strings.xml
+++ b/packages/InputDevices/res/values-sw/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Kiyahudi"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Kilithuania"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Kihispania (Kilatini)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Kilatvia"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-th/strings.xml b/packages/InputDevices/res/values-th/strings.xml
index 86a633a..296994b 100644
--- a/packages/InputDevices/res/values-th/strings.xml
+++ b/packages/InputDevices/res/values-th/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"ฮิบรู"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"ลิทัวเนีย"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"สเปน (ละติน)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"ลัตเวีย"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-tl/strings.xml b/packages/InputDevices/res/values-tl/strings.xml
index 702a0cb..d7920ed 100644
--- a/packages/InputDevices/res/values-tl/strings.xml
+++ b/packages/InputDevices/res/values-tl/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Hebrew"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Lithuanian"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Spanish (Latin)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Latvian"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-tr/strings.xml b/packages/InputDevices/res/values-tr/strings.xml
index b3ce0a3..c0c70be 100644
--- a/packages/InputDevices/res/values-tr/strings.xml
+++ b/packages/InputDevices/res/values-tr/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"İbranice"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Litvanca"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"İspanyolca (Latin)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Letonca"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-uk/strings.xml b/packages/InputDevices/res/values-uk/strings.xml
index 5193a90..d8152d4 100644
--- a/packages/InputDevices/res/values-uk/strings.xml
+++ b/packages/InputDevices/res/values-uk/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Іврит"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Литовська"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Іспанська (латиниця)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Латвійська"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-vi/strings.xml b/packages/InputDevices/res/values-vi/strings.xml
index 2240100..a90c1cd 100644
--- a/packages/InputDevices/res/values-vi/strings.xml
+++ b/packages/InputDevices/res/values-vi/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Tiếng Do Thái"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Tiếng Lithuania"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Tiếng Tây Ban Nha (La tinh)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Tiếng Latvia"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-zh-rCN/strings.xml b/packages/InputDevices/res/values-zh-rCN/strings.xml
index 161b600..206f97c 100644
--- a/packages/InputDevices/res/values-zh-rCN/strings.xml
+++ b/packages/InputDevices/res/values-zh-rCN/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"希伯来语"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"立陶宛语"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"西班牙语(拉丁美洲)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"拉脱维亚语"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-zh-rHK/strings.xml b/packages/InputDevices/res/values-zh-rHK/strings.xml
index f0df88c..ff5570e 100644
--- a/packages/InputDevices/res/values-zh-rHK/strings.xml
+++ b/packages/InputDevices/res/values-zh-rHK/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"希伯來文"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"立陶宛文"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"西班牙文 (拉丁美洲)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"拉脫維亞文"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-zh-rTW/strings.xml b/packages/InputDevices/res/values-zh-rTW/strings.xml
index ca43326..859983d 100644
--- a/packages/InputDevices/res/values-zh-rTW/strings.xml
+++ b/packages/InputDevices/res/values-zh-rTW/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"希伯來文"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"立陶宛文"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"西班牙文 (拉丁美洲)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"拉脫維亞文"</string>
 </resources>
diff --git a/packages/InputDevices/res/values-zu/strings.xml b/packages/InputDevices/res/values-zu/strings.xml
index 677ed29..9f30f7a 100644
--- a/packages/InputDevices/res/values-zu/strings.xml
+++ b/packages/InputDevices/res/values-zu/strings.xml
@@ -39,6 +39,5 @@
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Isi-Hebrew"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Isi-Lithuanian"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Isi-Spanish (Latin)"</string>
-    <!-- no translation found for keyboard_layout_latvian (4405417142306250595) -->
-    <skip />
+    <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Isi-Latvian"</string>
 </resources>
diff --git a/packages/Keyguard/Android.mk b/packages/Keyguard/Android.mk
index 1be44f9..96ed2e7 100644
--- a/packages/Keyguard/Android.mk
+++ b/packages/Keyguard/Android.mk
@@ -16,8 +16,7 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-subdir-Iaidl-files) \
-                   $(call all-proto-files-under,src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-subdir-Iaidl-files)
 
 LOCAL_MODULE := Keyguard
 
@@ -27,9 +26,6 @@
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
-LOCAL_PROTOC_OPTIMIZE_TYPE := nano
-LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimpleHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimpleHostView.java
index 3f6ced6..bc159cb 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimpleHostView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimpleHostView.java
@@ -25,6 +25,7 @@
 
     public KeyguardSimpleHostView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateCallback);
     }
 
     @Override
@@ -62,4 +63,10 @@
         // TODO Auto-generated method stub
     }
 
+    private KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
+        @Override
+        public void onUserSwitchComplete(int userId) {
+            getSecurityContainer().showPrimarySecurityScreen(false /* turning off */);
+        }
+    };
 }
diff --git a/packages/Keyguard/src/com/android/keyguard/analytics/KeyguardAnalytics.java b/packages/Keyguard/src/com/android/keyguard/analytics/KeyguardAnalytics.java
deleted file mode 100644
index 20af2f1..0000000
--- a/packages/Keyguard/src/com/android/keyguard/analytics/KeyguardAnalytics.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.keyguard.analytics;
-
-import com.google.protobuf.nano.CodedOutputByteBufferNano;
-import com.google.protobuf.nano.MessageNano;
-
-import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.os.AsyncTask;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * Tracks sessions, touch and sensor events in Keyguard.
- *
- * A session starts when the user is presented with the Keyguard and ends when the Keyguard is no
- * longer visible to the user.
- */
-public class KeyguardAnalytics implements SensorEventListener {
-
-    private static final boolean DEBUG = false;
-    private static final String TAG = "KeyguardAnalytics";
-    private static final long TIMEOUT_MILLIS = 11000; // 11 seconds.
-
-    private static final int[] SENSORS = new int[] {
-            Sensor.TYPE_ACCELEROMETER,
-            Sensor.TYPE_GYROSCOPE,
-            Sensor.TYPE_PROXIMITY,
-            Sensor.TYPE_LIGHT,
-            Sensor.TYPE_ROTATION_VECTOR,
-    };
-
-    private Session mCurrentSession = null;
-    // Err on the side of caution, so logging is not started after a crash even tough the screen
-    // is off.
-    private boolean mScreenOn = false;
-    private boolean mHidden = false;
-
-    private final SensorManager mSensorManager;
-    private final SessionTypeAdapter mSessionTypeAdapter;
-    private final File mAnalyticsFile;
-
-    public KeyguardAnalytics(Context context, SessionTypeAdapter sessionTypeAdapter,
-            File analyticsFile) {
-        mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
-        mSessionTypeAdapter = sessionTypeAdapter;
-        mAnalyticsFile = analyticsFile;
-    }
-
-    public Callback getCallback() {
-        return mCallback;
-    }
-
-    public interface Callback {
-        public void onShow();
-        public void onHide();
-        public void onScreenOn();
-        public void onScreenOff();
-        public boolean onTouchEvent(MotionEvent ev, int width, int height);
-        public void onSetOccluded(boolean hidden);
-    }
-
-    public interface SessionTypeAdapter {
-        public int getSessionType();
-    }
-
-    private void sessionEntrypoint() {
-        if (mCurrentSession == null && mScreenOn && !mHidden) {
-            onSessionStart();
-        }
-    }
-
-    private void sessionExitpoint(int result) {
-        if (mCurrentSession != null) {
-            onSessionEnd(result);
-        }
-    }
-
-    private void onSessionStart() {
-        int type = mSessionTypeAdapter.getSessionType();
-        mCurrentSession = new Session(System.currentTimeMillis(), System.nanoTime(), type);
-        if (type == Session.TYPE_KEYGUARD_SECURE) {
-            mCurrentSession.setRedactTouchEvents();
-        }
-        for (int sensorType : SENSORS) {
-            Sensor s = mSensorManager.getDefaultSensor(sensorType);
-            if (s != null) {
-                mSensorManager.registerListener(this, s, SensorManager.SENSOR_DELAY_GAME);
-            }
-        }
-        if (DEBUG) {
-            Log.d(TAG, "onSessionStart()");
-        }
-    }
-
-    private void onSessionEnd(int result) {
-        if (DEBUG) {
-            Log.d(TAG, String.format("onSessionEnd(success=%d)", result));
-        }
-        mSensorManager.unregisterListener(this);
-
-        Session session = mCurrentSession;
-        mCurrentSession = null;
-
-        session.end(System.currentTimeMillis(), result);
-        queueSession(session);
-    }
-
-    private void queueSession(final Session currentSession) {
-        if (DEBUG) {
-            Log.i(TAG, "Saving session.");
-        }
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                try {
-                    byte[] b = writeDelimitedProto(currentSession.toProto());
-                    OutputStream os = new FileOutputStream(mAnalyticsFile, true /* append */);
-                    if (DEBUG) {
-                        Log.d(TAG, String.format("Serialized size: %d kB.", b.length / 1024));
-                    }
-                    try {
-                        os.write(b);
-                        os.flush();
-                    } finally {
-                        try {
-                            os.close();
-                        } catch (IOException e) {
-                            Log.e(TAG, "Exception while closing file", e);
-                        }
-                    }
-                } catch (IOException e) {
-                    Log.e(TAG, "Exception while writing file", e);
-                }
-                return null;
-            }
-
-            private byte[] writeDelimitedProto(MessageNano proto)
-                    throws IOException {
-                byte[] result = new byte[CodedOutputByteBufferNano.computeMessageSizeNoTag(proto)];
-                CodedOutputByteBufferNano ob = CodedOutputByteBufferNano.newInstance(result);
-                ob.writeMessageNoTag(proto);
-                ob.checkNoSpaceLeft();
-                return result;
-            }
-        }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
-    }
-
-    @Override
-    public synchronized void onSensorChanged(SensorEvent event) {
-        if (false) {
-            Log.v(TAG, String.format(
-                    "onSensorChanged(name=%s, values[0]=%f)",
-                    event.sensor.getName(), event.values[0]));
-        }
-        if (mCurrentSession != null) {
-            mCurrentSession.addSensorEvent(event, System.nanoTime());
-            enforceTimeout();
-        }
-    }
-
-    private void enforceTimeout() {
-        if (System.currentTimeMillis() - mCurrentSession.getStartTimestampMillis()
-                > TIMEOUT_MILLIS) {
-            onSessionEnd(Session.RESULT_UNKNOWN);
-            if (DEBUG) {
-                Log.i(TAG, "Analytics timed out.");
-            }
-        }
-    }
-
-    @Override
-    public void onAccuracyChanged(Sensor sensor, int accuracy) {
-    }
-
-    private final Callback mCallback = new Callback() {
-        @Override
-        public void onShow() {
-            if (DEBUG) {
-                Log.d(TAG, "onShow()");
-            }
-            synchronized (KeyguardAnalytics.this) {
-                sessionEntrypoint();
-            }
-        }
-
-        @Override
-        public void onHide() {
-            if (DEBUG) {
-                Log.d(TAG, "onHide()");
-            }
-            synchronized (KeyguardAnalytics.this) {
-                sessionExitpoint(Session.RESULT_SUCCESS);
-            }
-        }
-
-        @Override
-        public void onScreenOn() {
-            if (DEBUG) {
-                Log.d(TAG, "onScreenOn()");
-            }
-            synchronized (KeyguardAnalytics.this) {
-                mScreenOn = true;
-                sessionEntrypoint();
-            }
-        }
-
-        @Override
-        public void onScreenOff() {
-            if (DEBUG) {
-                Log.d(TAG, "onScreenOff()");
-            }
-            synchronized (KeyguardAnalytics.this) {
-                mScreenOn = false;
-                sessionExitpoint(Session.RESULT_FAILURE);
-            }
-        }
-
-        @Override
-        public boolean onTouchEvent(MotionEvent ev, int width, int height) {
-            if (DEBUG) {
-                Log.v(TAG, "onTouchEvent(ev.action="
-                        + MotionEvent.actionToString(ev.getAction()) + ")");
-            }
-            synchronized (KeyguardAnalytics.this) {
-                if (mCurrentSession != null) {
-                    mCurrentSession.addMotionEvent(ev);
-                    mCurrentSession.setTouchArea(width, height);
-                    enforceTimeout();
-                }
-            }
-            return true;
-        }
-
-        @Override
-        public void onSetOccluded(boolean hidden) {
-            synchronized (KeyguardAnalytics.this) {
-                if (hidden != mHidden) {
-                    if (DEBUG) {
-                        Log.d(TAG, "onSetOccluded(" + hidden + ")");
-                    }
-                    mHidden = hidden;
-                    if (hidden) {
-                        // Could have gone to camera on purpose / by falsing or an app could have
-                        // launched on top of the lockscreen.
-                        sessionExitpoint(Session.RESULT_UNKNOWN);
-                    } else {
-                        sessionEntrypoint();
-                    }
-                }
-            }
-        }
-    };
-
-}
diff --git a/packages/Keyguard/src/com/android/keyguard/analytics/PointerTracker.java b/packages/Keyguard/src/com/android/keyguard/analytics/PointerTracker.java
deleted file mode 100644
index e68f751..0000000
--- a/packages/Keyguard/src/com/android/keyguard/analytics/PointerTracker.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.keyguard.analytics;
-
-import android.graphics.RectF;
-import android.util.FloatMath;
-import android.util.SparseArray;
-import android.view.MotionEvent;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import static com.android.keyguard.analytics.KeyguardAnalyticsProtos.Session.TouchEvent.BoundingBox;
-
-/**
- * Takes motion events and tracks the length and bounding box of each pointer gesture as well as
- * the bounding box of the whole gesture.
- */
-public class PointerTracker {
-    private SparseArray<Pointer> mPointerInfoMap = new SparseArray<Pointer>();
-    private RectF mTotalBoundingBox = new RectF();
-
-    public void addMotionEvent(MotionEvent ev) {
-        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            float x = ev.getX();
-            float y = ev.getY();
-            mTotalBoundingBox.set(x, y, x, y);
-        }
-        for (int i = 0; i < ev.getPointerCount(); i++) {
-            int id = ev.getPointerId(i);
-            Pointer pointer = getPointer(id);
-            float x = ev.getX(i);
-            float y = ev.getY(i);
-            boolean down = ev.getActionMasked() == MotionEvent.ACTION_DOWN
-                    || (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
-                            && ev.getActionIndex() == i);
-            pointer.addPoint(x, y, down);
-            mTotalBoundingBox.union(x, y);
-        }
-    }
-
-    public float getPointerLength(int id) {
-        return getPointer(id).length;
-    }
-
-    public BoundingBox getBoundingBox() {
-        return boundingBoxFromRect(mTotalBoundingBox);
-    }
-
-    public BoundingBox getPointerBoundingBox(int id) {
-        return boundingBoxFromRect(getPointer(id).boundingBox);
-    }
-
-    private BoundingBox boundingBoxFromRect(RectF f) {
-        BoundingBox bb = new BoundingBox();
-        bb.setHeight(f.height());
-        bb.setWidth(f.width());
-        return bb;
-    }
-
-    private Pointer getPointer(int id) {
-        Pointer p = mPointerInfoMap.get(id);
-        if (p == null) {
-            p = new Pointer();
-            mPointerInfoMap.put(id, p);
-        }
-        return p;
-    }
-
-    private static class Pointer {
-        public float length;
-        public final RectF boundingBox = new RectF();
-
-        private float mLastX;
-        private float mLastY;
-
-        public void addPoint(float x, float y, boolean down) {
-            float deltaX;
-            float deltaY;
-            if (down) {
-                boundingBox.set(x, y, x, y);
-                length = 0f;
-                deltaX = 0;
-                deltaY = 0;
-            } else {
-                deltaX = x - mLastX;
-                deltaY = y - mLastY;
-            }
-            mLastX = x;
-            mLastY = y;
-            length += FloatMath.sqrt(deltaX * deltaX + deltaY * deltaY);
-            boundingBox.union(x, y);
-        }
-    }
-}
diff --git a/packages/Keyguard/src/com/android/keyguard/analytics/Session.java b/packages/Keyguard/src/com/android/keyguard/analytics/Session.java
deleted file mode 100644
index 05f9165..0000000
--- a/packages/Keyguard/src/com/android/keyguard/analytics/Session.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.keyguard.analytics;
-
-import android.os.Build;
-import android.util.Slog;
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-
-import static com.android.keyguard.analytics.KeyguardAnalyticsProtos.Session.SensorEvent;
-import static com.android.keyguard.analytics.KeyguardAnalyticsProtos.Session.TouchEvent;
-
-/**
- * Records data about one keyguard session.
- *
- * The recorded data contains start and end of the session, whether it unlocked the device
- * successfully, sensor data and touch data.
- *
- * If the keyguard is secure, the recorded touch data will correlate or contain the user pattern or
- * PIN. If this is not desired, the touch coordinates can be redacted before serialization.
- */
-public class Session {
-
-    private static final String TAG = "KeyguardAnalytics";
-    private static final boolean DEBUG = false;
-
-    /**
-     * The user has failed to unlock the device in this session.
-     */
-    public static final int RESULT_FAILURE = KeyguardAnalyticsProtos.Session.FAILURE;
-    /**
-     * The user has succeeded in unlocking the device in this session.
-     */
-    public static final int RESULT_SUCCESS = KeyguardAnalyticsProtos.Session.SUCCESS;
-
-    /**
-     * It is unknown how the session with the keyguard ended.
-     */
-    public static final int RESULT_UNKNOWN = KeyguardAnalyticsProtos.Session.UNKNOWN;
-
-    /**
-     * This session took place on an insecure keyguard.
-     */
-    public static final int TYPE_KEYGUARD_INSECURE
-            = KeyguardAnalyticsProtos.Session.KEYGUARD_INSECURE;
-
-    /**
-     * This session took place on an secure keyguard.
-     */
-    public static final int TYPE_KEYGUARD_SECURE
-            = KeyguardAnalyticsProtos.Session.KEYGUARD_SECURE;
-
-    /**
-     * This session took place during a fake wake up of the device.
-     */
-    public static final int TYPE_RANDOM_WAKEUP = KeyguardAnalyticsProtos.Session.RANDOM_WAKEUP;
-
-
-    private final PointerTracker mPointerTracker = new PointerTracker();
-
-    private final long mStartTimestampMillis;
-    private final long mStartSystemTimeNanos;
-    private final int mType;
-
-    private boolean mRedactTouchEvents;
-    private ArrayList<TouchEvent> mMotionEvents = new ArrayList<TouchEvent>(200);
-    private ArrayList<SensorEvent> mSensorEvents = new ArrayList<SensorEvent>(600);
-    private int mTouchAreaHeight;
-    private int mTouchAreaWidth;
-
-    private long mEndTimestampMillis;
-    private int mResult;
-    private boolean mEnded;
-
-    public Session(long startTimestampMillis, long startSystemTimeNanos, int type) {
-        mStartTimestampMillis = startTimestampMillis;
-        mStartSystemTimeNanos = startSystemTimeNanos;
-        mType = type;
-    }
-
-    public void end(long endTimestampMillis, int result) {
-        mEnded = true;
-        mEndTimestampMillis = endTimestampMillis;
-        mResult = result;
-    }
-
-    public void addMotionEvent(MotionEvent motionEvent) {
-        if (mEnded) {
-            return;
-        }
-        mPointerTracker.addMotionEvent(motionEvent);
-        mMotionEvents.add(protoFromMotionEvent(motionEvent));
-    }
-
-    public void addSensorEvent(android.hardware.SensorEvent eventOrig, long systemTimeNanos) {
-        if (mEnded) {
-            return;
-        }
-        SensorEvent event = protoFromSensorEvent(eventOrig, systemTimeNanos);
-        mSensorEvents.add(event);
-        if (DEBUG) {
-            Slog.v(TAG, String.format("addSensorEvent(name=%s, values[0]=%f",
-                    event.getType(), event.values[0]));
-        }
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder sb = new StringBuilder("Session{");
-        sb.append("mType=").append(mType);
-        sb.append(", mStartTimestampMillis=").append(mStartTimestampMillis);
-        sb.append(", mStartSystemTimeNanos=").append(mStartSystemTimeNanos);
-        sb.append(", mEndTimestampMillis=").append(mEndTimestampMillis);
-        sb.append(", mResult=").append(mResult);
-        sb.append(", mRedactTouchEvents=").append(mRedactTouchEvents);
-        sb.append(", mTouchAreaHeight=").append(mTouchAreaHeight);
-        sb.append(", mTouchAreaWidth=").append(mTouchAreaWidth);
-        sb.append(", mMotionEvents=[size=").append(mMotionEvents.size()).append("]");
-        sb.append(", mSensorEvents=[size=").append(mSensorEvents.size()).append("]");
-        sb.append('}');
-        return sb.toString();
-    }
-
-    public KeyguardAnalyticsProtos.Session toProto() {
-        KeyguardAnalyticsProtos.Session proto = new KeyguardAnalyticsProtos.Session();
-        proto.setStartTimestampMillis(mStartTimestampMillis);
-        proto.setDurationMillis(mEndTimestampMillis - mStartTimestampMillis);
-        proto.setBuild(Build.FINGERPRINT);
-        proto.setResult(mResult);
-        proto.sensorEvents = mSensorEvents.toArray(proto.sensorEvents);
-        proto.touchEvents = mMotionEvents.toArray(proto.touchEvents);
-        proto.setTouchAreaWidth(mTouchAreaWidth);
-        proto.setTouchAreaHeight(mTouchAreaHeight);
-        proto.setType(mType);
-        if (mRedactTouchEvents) {
-            redactTouchEvents(proto.touchEvents);
-        }
-        return proto;
-    }
-
-    private void redactTouchEvents(TouchEvent[] touchEvents) {
-        for (int i = 0; i < touchEvents.length; i++) {
-            TouchEvent t = touchEvents[i];
-            for (int j = 0; j < t.pointers.length; j++) {
-                TouchEvent.Pointer p = t.pointers[j];
-                p.clearX();
-                p.clearY();
-            }
-            t.setRedacted(true);
-        }
-    }
-
-    private SensorEvent protoFromSensorEvent(android.hardware.SensorEvent ev, long sysTimeNanos) {
-        SensorEvent proto = new SensorEvent();
-        proto.setType(ev.sensor.getType());
-        proto.setTimeOffsetNanos(sysTimeNanos - mStartSystemTimeNanos);
-        proto.setTimestamp(ev.timestamp);
-        proto.values = ev.values.clone();
-        return proto;
-    }
-
-    private TouchEvent protoFromMotionEvent(MotionEvent ev) {
-        int count = ev.getPointerCount();
-        TouchEvent proto = new TouchEvent();
-        proto.setTimeOffsetNanos(ev.getEventTimeNano() - mStartSystemTimeNanos);
-        proto.setAction(ev.getActionMasked());
-        proto.setActionIndex(ev.getActionIndex());
-        proto.pointers = new TouchEvent.Pointer[count];
-        for (int i = 0; i < count; i++) {
-            TouchEvent.Pointer p = new TouchEvent.Pointer();
-            p.setX(ev.getX(i));
-            p.setY(ev.getY(i));
-            p.setSize(ev.getSize(i));
-            p.setPressure(ev.getPressure(i));
-            p.setId(ev.getPointerId(i));
-            proto.pointers[i] = p;
-            if ((ev.getActionMasked() == MotionEvent.ACTION_POINTER_UP && ev.getActionIndex() == i)
-                    || ev.getActionMasked() == MotionEvent.ACTION_UP) {
-                p.boundingBox = mPointerTracker.getPointerBoundingBox(p.getId());
-                p.setLength(mPointerTracker.getPointerLength(p.getId()));
-            }
-        }
-        if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
-            proto.boundingBox = mPointerTracker.getBoundingBox();
-        }
-        return proto;
-    }
-
-    /**
-     * Discards the x / y coordinates of the touch events on serialization. Retained are the
-     * size of the individual and overall bounding boxes and the length of each pointer's gesture.
-     */
-    public void setRedactTouchEvents() {
-        mRedactTouchEvents = true;
-    }
-
-    public void setTouchArea(int width, int height) {
-        mTouchAreaWidth = width;
-        mTouchAreaHeight = height;
-    }
-
-    public long getStartTimestampMillis() {
-        return mStartTimestampMillis;
-    }
-}
diff --git a/packages/Keyguard/src/com/android/keyguard/analytics/keyguard_analytics.proto b/packages/Keyguard/src/com/android/keyguard/analytics/keyguard_analytics.proto
deleted file mode 100644
index 68b1590..0000000
--- a/packages/Keyguard/src/com/android/keyguard/analytics/keyguard_analytics.proto
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-syntax = "proto2";
-
-package keyguard;
-
-option java_package = "com.android.keyguard.analytics";
-option java_outer_classname = "KeyguardAnalyticsProtos";
-
-message Session {
-    message TouchEvent {
-        message BoundingBox {
-            optional float width = 1;
-            optional float height = 2;
-        }
-
-        enum Action {
-            // Keep in sync with MotionEvent.
-            DOWN = 0;
-            UP = 1;
-            MOVE = 2;
-            CANCEL = 3;
-            OUTSIDE = 4;
-            POINTER_DOWN = 5;
-            POINTER_UP = 6;
-        }
-
-        message Pointer {
-            optional float x = 1;
-            optional float y = 2;
-            optional float size = 3;
-            optional float pressure = 4;
-            optional int32 id = 5;
-            optional float length = 6;
-            // Bounding box of the pointer. Only set on UP or POINTER_UP event of this pointer.
-            optional BoundingBox boundingBox = 7;
-        }
-
-        optional uint64 timeOffsetNanos = 1;
-        optional Action action = 2;
-        optional int32 actionIndex = 3;
-        repeated Pointer pointers = 4;
-        /* If true, the the x / y coordinates of the touch events were redacted. Retained are the
-           size of the individual and overall bounding boxes and the length of each pointer's
-           gesture. */
-        optional bool redacted = 5;
-        // Bounding box of the whole gesture. Only set on UP event.
-        optional BoundingBox boundingBox = 6;
-    }
-
-    message SensorEvent {
-        enum Type {
-            ACCELEROMETER = 1;
-            GYROSCOPE = 4;
-            LIGHT = 5;
-            PROXIMITY = 8;
-            ROTATION_VECTOR = 11;
-        }
-
-        optional Type type = 1;
-        optional uint64 timeOffsetNanos = 2;
-        repeated float values = 3;
-        optional uint64 timestamp = 4;
-    }
-
-    enum Result {
-        FAILURE = 0;
-        SUCCESS = 1;
-        UNKNOWN = 2;
-    }
-
-    enum Type {
-        KEYGUARD_INSECURE = 0;
-        KEYGUARD_SECURE = 1;
-        RANDOM_WAKEUP = 2;
-    }
-
-    optional uint64 startTimestampMillis = 1;
-    optional uint64 durationMillis = 2;
-    optional string build = 3;
-    optional Result result = 4;
-    repeated TouchEvent touchEvents = 5;
-    repeated SensorEvent sensorEvents = 6;
-
-    optional int32 touchAreaWidth = 9;
-    optional int32 touchAreaHeight = 10;
-    optional Type type = 11;
-}
diff --git a/packages/PrintSpooler/Android.mk b/packages/PrintSpooler/Android.mk
index 9e7b969..96592b4 100644
--- a/packages/PrintSpooler/Android.mk
+++ b/packages/PrintSpooler/Android.mk
@@ -24,4 +24,6 @@
 
 LOCAL_JAVA_LIBRARIES := framework-base
 
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+
 include $(BUILD_PACKAGE)
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index e1d0aec..4c0bbb8 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -42,23 +42,23 @@
     <uses-permission android:name="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"/>
 
     <application
-            android:allowClearUserData="true"
-            android:label="@string/app_label"
-            android:allowBackup= "false"
-            android:supportsRtl="true"
-            android:icon="@*android:drawable/ic_print">
+        android:allowClearUserData="true"
+        android:label="@string/app_label"
+        android:allowBackup= "false"
+        android:supportsRtl="true"
+        android:icon="@*android:drawable/ic_print">
 
         <service
-            android:name=".PrintSpoolerService"
+            android:name=".model.PrintSpoolerService"
             android:exported="true"
             android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE">
         </service>
 
         <activity
-            android:name=".PrintJobConfigActivity"
+            android:name=".ui.PrintActivity"
             android:configChanges="orientation|screenSize"
             android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE"
-            android:theme="@style/PrintJobConfigActivityTheme">
+            android:theme="@android:style/Theme.DeviceDefault.NoActionBar">
             <intent-filter>
                 <action android:name="android.print.PRINT_DIALOG" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -67,7 +67,7 @@
         </activity>
 
         <activity
-            android:name=".SelectPrinterActivity"
+            android:name=".ui.SelectPrinterActivity"
             android:label="@string/all_printers_label"
             android:theme="@style/SelectPrinterActivityTheme"
             android:exported="false">
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less_24dp.png b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less_24dp.png
new file mode 100644
index 0000000..d2e5408
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more_24dp.png b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more_24dp.png
new file mode 100644
index 0000000..f4c4b0c
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_grayedout_printer.png b/packages/PrintSpooler/res/drawable-hdpi/ic_grayedout_printer.png
new file mode 100644
index 0000000..5e54970
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-hdpi/ic_grayedout_printer.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/print_button_background.png b/packages/PrintSpooler/res/drawable-hdpi/print_button_background.png
new file mode 100644
index 0000000..88e8495
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-hdpi/print_button_background.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less_24dp.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less_24dp.png
new file mode 100644
index 0000000..3220eea
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more_24dp.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more_24dp.png
new file mode 100644
index 0000000..5530f52
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_grayedout_printer.png b/packages/PrintSpooler/res/drawable-mdpi/ic_grayedout_printer.png
new file mode 100644
index 0000000..5e54970
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-mdpi/ic_grayedout_printer.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/print_button_background.png b/packages/PrintSpooler/res/drawable-mdpi/print_button_background.png
new file mode 100644
index 0000000..3a37b27
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-mdpi/print_button_background.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less_24dp.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less_24dp.png
new file mode 100644
index 0000000..f0074275
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more_24dp.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more_24dp.png
new file mode 100644
index 0000000..43debb3
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_grayedout_printer.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_grayedout_printer.png
new file mode 100644
index 0000000..5e54970
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xhdpi/ic_grayedout_printer.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/print_button_background.png b/packages/PrintSpooler/res/drawable-xhdpi/print_button_background.png
new file mode 100644
index 0000000..b2ed8cd
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xhdpi/print_button_background.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less_24dp.png b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less_24dp.png
new file mode 100644
index 0000000..39bc2ba
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more_24dp.png b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more_24dp.png
new file mode 100644
index 0000000..664f3f2
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less_24dp.png b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less_24dp.png
new file mode 100644
index 0000000..fe9c539
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more_24dp.png b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more_24dp.png
new file mode 100644
index 0000000..18d075c
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more_24dp.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable/ic_expand_less.xml b/packages/PrintSpooler/res/drawable/ic_expand_less.xml
new file mode 100644
index 0000000..b0c7d51
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/ic_expand_less.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:autoMirrored="true">
+
+    <item
+        android:state_checked="true">
+        <bitmap
+            android:src="@drawable/ic_expand_less_24dp"
+            android:tint="?android:attr/colorControlActivated">
+        </bitmap>
+    </item>
+
+    <item
+        android:state_pressed="true">
+        <bitmap
+            android:src="@drawable/ic_expand_less_24dp"
+            android:tint="?android:attr/colorControlActivated">
+        </bitmap>
+    </item>
+
+    <item>
+        <bitmap
+            android:src="@drawable/ic_expand_less_24dp"
+            android:tint="?android:attr/colorControlNormal">
+        </bitmap>
+    </item>
+
+</selector>
diff --git a/packages/PrintSpooler/res/drawable/ic_expand_more.xml b/packages/PrintSpooler/res/drawable/ic_expand_more.xml
new file mode 100644
index 0000000..b809c25
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/ic_expand_more.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:autoMirrored="true">
+
+    <item
+        android:state_checked="true">
+        <bitmap
+            android:src="@drawable/ic_expand_more_24dp"
+            android:tint="?android:attr/colorControlActivated">
+        </bitmap>
+    </item>
+
+    <item
+        android:state_pressed="true">
+        <bitmap
+            android:src="@drawable/ic_expand_more_24dp"
+            android:tint="?android:attr/colorControlActivated">
+        </bitmap>
+    </item>
+
+    <item>
+        <bitmap
+            android:src="@drawable/ic_expand_more_24dp"
+            android:tint="?android:attr/colorControlNormal">
+        </bitmap>
+    </item>
+
+</selector>
diff --git a/packages/PrintSpooler/res/drawable/print_button.xml b/packages/PrintSpooler/res/drawable/print_button.xml
new file mode 100644
index 0000000..b59afba
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/print_button.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/print_button_tint_color">
+    <item
+        android:drawable="@drawable/print_button_background">
+    </item>
+</ripple>
diff --git a/packages/PrintSpooler/res/layout/print_activity.xml b/packages/PrintSpooler/res/layout/print_activity.xml
new file mode 100644
index 0000000..9715322
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/print_activity.xml
@@ -0,0 +1,389 @@
+<?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.
+-->
+
+<com.android.printspooler.widget.ContentView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:printspooler="http://schemas.android.com/apk/res/com.android.printspooler"
+    android:id="@+id/options_content"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:visibility="invisible"
+    android:background="?android:attr/colorForeground">
+
+    <FrameLayout
+        android:id="@+id/static_content"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:padding="16dip"
+        android:background="?android:attr/colorForegroundInverse">
+
+        <!-- Destination -->
+
+        <Spinner
+            android:id="@+id/destination_spinner"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:dropDownWidth="wrap_content"
+            android:minHeight="?android:attr/listPreferredItemHeightSmall">
+        </Spinner>
+
+    </FrameLayout>
+
+    <!-- Summary -->
+
+    <LinearLayout
+        android:id="@+id/summary_content"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="16dip"
+        android:paddingEnd="16dip"
+        android:orientation="horizontal"
+        android:background="?android:attr/colorForegroundInverse">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dip"
+            android:layout_marginStart="12dip"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:labelFor="@+id/copies_count_summary"
+            android:text="@string/label_copies_summary">
+        </TextView>
+
+        <TextView
+            android:id="@+id/copies_count_summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dip"
+            android:layout_marginStart="16dip"
+            android:textAppearance="?android:attr/textAppearanceMedium">
+        </TextView>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dip"
+            android:layout_marginStart="32dip"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:labelFor="@+id/paper_size_summary"
+            android:text="@string/label_paper_size_summary">
+        </TextView>
+
+        <TextView
+            android:id="@+id/paper_size_summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dip"
+            android:layout_marginStart="16dip"
+            android:textAppearance="?android:attr/textAppearanceMedium">
+        </TextView>
+
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@+id/dynamic_content"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:paddingBottom="16dip">
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <LinearLayout
+                android:id="@+id/draggable_content"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <com.android.printspooler.widget.PrintOptionsLayout
+                    android:id="@+id/options_container"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:background="?android:attr/colorForegroundInverse"
+                    printspooler:columnCount="@integer/print_option_column_count">
+
+                    <LinearLayout
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="16dip"
+                        android:layout_marginEnd="16dip"
+                        android:orientation="vertical">
+
+                        <!-- Copies -->
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="8dip"
+                            android:layout_marginStart="12dip"
+                            android:textAppearance="?android:attr/textAppearanceSmall"
+                            android:labelFor="@+id/copies_edittext"
+                            android:text="@string/label_copies">
+                        </TextView>
+
+                        <view
+                            class="com.android.printspooler.widget.FirstFocusableEditText"
+                            android:id="@+id/copies_edittext"
+                            android:layout_width="fill_parent"
+                            android:layout_height="wrap_content"
+                            style="?android:attr/editTextStyle"
+                            android:inputType="numberDecimal">
+                        </view>
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="16dip"
+                        android:layout_marginEnd="16dip"
+                        android:orientation="vertical">
+
+                        <!-- Paper size -->
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="8dip"
+                            android:layout_marginStart="12dip"
+                            android:textAppearance="?android:attr/textAppearanceSmall"
+                            android:labelFor="@+id/paper_size_spinner"
+                            android:text="@string/label_paper_size">
+                        </TextView>
+
+                        <Spinner
+                            android:id="@+id/paper_size_spinner"
+                            android:layout_width="fill_parent"
+                            android:layout_height="wrap_content"
+                            style="@style/PrintOptionSpinnerStyle">
+                        </Spinner>
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="16dip"
+                        android:layout_marginEnd="16dip"
+                        android:orientation="vertical">
+
+                        <!-- Color -->
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="8dip"
+                            android:layout_marginStart="12dip"
+                            android:textAppearance="?android:attr/textAppearanceSmall"
+                            android:labelFor="@+id/color_spinner"
+                            android:text="@string/label_color">
+                        </TextView>
+
+                        <Spinner
+                            android:id="@+id/color_spinner"
+                            android:layout_width="fill_parent"
+                            android:layout_height="wrap_content"
+                            style="@style/PrintOptionSpinnerStyle">
+                        </Spinner>
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="16dip"
+                        android:layout_marginEnd="16dip"
+                        android:orientation="vertical">
+
+                        <!-- Orientation -->
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="8dip"
+                            android:layout_marginStart="12dip"
+                            android:textAppearance="?android:attr/textAppearanceSmall"
+                            android:labelFor="@+id/orientation_spinner"
+                            android:text="@string/label_orientation">
+                        </TextView>
+
+                        <Spinner
+                            android:id="@+id/orientation_spinner"
+                            android:layout_width="fill_parent"
+                            android:layout_height="wrap_content"
+                            style="@style/PrintOptionSpinnerStyle">
+                        </Spinner>
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="16dip"
+                        android:layout_marginEnd="16dip"
+                        android:orientation="vertical">
+
+                        <!-- Range options -->
+
+                        <TextView
+                            android:id="@+id/range_options_title"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="8dip"
+                            android:layout_marginStart="12dip"
+                            android:textAppearance="?android:attr/textAppearanceSmall"
+                            android:labelFor="@+id/range_options_spinner"
+                            android:text="@string/page_count_unknown">
+                        </TextView>
+
+                        <Spinner
+                            android:id="@+id/range_options_spinner"
+                            android:layout_width="fill_parent"
+                            android:layout_height="wrap_content"
+                            style="@style/PrintOptionSpinnerStyle">
+                        </Spinner>
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="16dip"
+                        android:layout_marginEnd="16dip"
+                        android:orientation="vertical">
+
+                        <!-- Pages -->
+
+                        <TextView
+                            android:id="@+id/page_range_title"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="8dip"
+                            android:layout_marginStart="12dip"
+                            android:textAppearance="?android:attr/textAppearanceSmall"
+                            android:text="@string/pages_range_example"
+                            android:labelFor="@+id/page_range_edittext"
+                            android:textAllCaps="false"
+                            android:visibility="visible">
+                        </TextView>
+
+                        <view
+                            class="com.android.printspooler.widget.FirstFocusableEditText"
+                            android:id="@+id/page_range_edittext"
+                            android:layout_width="fill_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="bottom|fill_horizontal"
+                            style="@style/PrintOptionEditTextStyle"
+                            android:visibility="visible"
+                            android:inputType="textNoSuggestions">
+                        </view>
+
+                    </LinearLayout>
+
+                </com.android.printspooler.widget.PrintOptionsLayout>
+
+                <!-- More options -->
+
+                <LinearLayout
+                    android:id="@+id/more_options_container"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingStart="28dip"
+                    android:paddingEnd="28dip"
+                    android:orientation="vertical"
+                    android:visibility="visible"
+                    android:background="?android:attr/colorForegroundInverse">
+
+                    <ImageView
+                        android:layout_width="fill_parent"
+                        android:layout_height="1dip"
+                        android:layout_gravity="fill_horizontal"
+                        android:background="?android:attr/colorControlNormal"
+                        android:contentDescription="@null">
+                    </ImageView>
+
+                    <Button
+                        android:id="@+id/more_options_button"
+                        style="?android:attr/borderlessButtonStyle"
+                        android:layout_width="fill_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_gravity="fill_horizontal"
+                        android:text="@string/more_options_button"
+                        android:gravity="start|center_vertical"
+                        android:textAllCaps="false">
+                    </Button>
+
+                    <ImageView
+                        android:layout_width="fill_parent"
+                        android:layout_height="1dip"
+                        android:layout_gravity="fill_horizontal"
+                        android:background="?android:attr/colorControlNormal"
+                        android:contentDescription="@null">
+                    </ImageView>
+
+                </LinearLayout>
+
+            </LinearLayout>
+
+            <!-- Expand/collapse handle -->
+
+            <FrameLayout
+                android:id="@+id/expand_collapse_handle"
+                android:layout_width="fill_parent"
+                android:layout_height="?android:attr/listPreferredItemHeightSmall"
+                android:layout_marginBottom="28dip"
+                android:background="?android:attr/colorForegroundInverse"
+                android:elevation="12dip">
+
+                <ImageButton
+                    android:id="@+id/expand_collapse_icon"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:background="@drawable/ic_expand_more">
+                </ImageButton>
+
+            </FrameLayout>
+
+        </LinearLayout>
+
+        <!-- Print button -->
+
+        <ImageButton
+            android:id="@+id/print_button"
+            style="?android:attr/buttonStyleSmall"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="end|bottom"
+            android:layout_marginEnd="16dip"
+            android:elevation="12dip"
+            android:background="@drawable/print_button"
+            android:src="@*android:drawable/ic_print">
+        </ImageButton>
+
+    </FrameLayout>
+
+
+    <FrameLayout
+        android:id="@+id/embedded_content_container"
+        android:layout_width="fill_parent"
+        android:layout_height="0dip"
+        android:animateLayoutChanges="true">
+    </FrameLayout>
+
+</com.android.printspooler.widget.ContentView>
diff --git a/packages/PrintSpooler/res/layout/print_error_fragment.xml b/packages/PrintSpooler/res/layout/print_error_fragment.xml
new file mode 100644
index 0000000..dc44339
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/print_error_fragment.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="12dip"
+        android:src="@drawable/ic_grayedout_printer"
+        android:contentDescription="@null">
+    </ImageView>
+
+    <TextView
+        android:id="@+id/message"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/print_error_default_message"
+        android:textAppearance="?android:attr/textAppearanceLargeInverse">
+    </TextView>
+
+    <Button
+        android:id="@+id/action_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/print_error_retry">
+    </Button>
+
+</LinearLayout>
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml
deleted file mode 100644
index 3303ef1..0000000
--- a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml
+++ /dev/null
@@ -1,26 +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.
--->
-
-<com.android.printspooler.PrintDialogFrame xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content">
-    <FrameLayout
-        android:id="@+id/content_container"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
-        android:background="@color/container_background">
-    </FrameLayout>
-</com.android.printspooler.PrintDialogFrame>
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml
deleted file mode 100644
index e50a7af..0000000
--- a/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml
+++ /dev/null
@@ -1,282 +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.
--->
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical"
-    android:scrollbars="vertical">
-
-    <LinearLayout
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <!-- Destination -->
-
-        <Spinner
-            android:id="@+id/destination_spinner"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:layout_gravity="fill_horizontal"
-            android:layout_marginTop="24dip"
-            android:layout_marginStart="24dip"
-            android:layout_marginEnd="24dip"
-            android:minHeight="?android:attr/listPreferredItemHeightSmall">
-        </Spinner>
-
-        <LinearLayout
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginBottom="24dip"
-            android:orientation="horizontal"
-            android:baselineAligned="false">
-
-            <LinearLayout
-                android:layout_width="0dip"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:orientation="vertical">
-
-                <!-- Copies -->
-
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="12dip"
-                    android:layout_marginStart="36dip"
-                    android:layout_marginEnd="6dip"
-                    android:textAppearance="@style/PrintOptionTitleTextAppearance"
-                    android:labelFor="@+id/copies_edittext"
-                    android:text="@string/label_copies">
-                </TextView>
-
-                <view
-                    class="com.android.printspooler.PrintJobConfigActivity$CustomEditText"
-                    android:id="@+id/copies_edittext"
-                    android:layout_width="fill_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="24dip"
-                    android:layout_marginEnd="6dip"
-                    style="@style/PrintOptionEditTextStyle"
-                    android:inputType="numberDecimal">
-                </view>
-
-                <!-- Color -->
-
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="12dip"
-                    android:layout_marginStart="36dip"
-                    android:layout_marginEnd="6dip"
-                    android:textAppearance="@style/PrintOptionTitleTextAppearance"
-                    android:labelFor="@+id/color_spinner"
-                    android:text="@string/label_color">
-                </TextView>
-
-                <Spinner
-                    android:id="@+id/color_spinner"
-                    android:layout_width="fill_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="24dip"
-                    android:layout_marginEnd="6dip"
-                    style="@style/PrintOptionSpinnerStyle">
-                </Spinner>
-
-                <!-- Range options -->
-
-                <TextView
-                    android:id="@+id/range_options_title"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="12dip"
-                    android:layout_marginStart="36dip"
-                    android:textAppearance="@style/PrintOptionTitleTextAppearance"
-                    android:labelFor="@+id/range_options_spinner"
-                    android:text="@string/page_count_unknown">
-                </TextView>
-
-                <Spinner
-                    android:id="@+id/range_options_spinner"
-                    android:layout_width="fill_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="24dip"
-                    android:layout_marginEnd="6dip"
-                    style="@style/PrintOptionSpinnerStyle">
-                </Spinner>
-
-            </LinearLayout>
-
-            <LinearLayout
-                android:layout_width="0dip"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:orientation="vertical">
-
-                <!-- Paper size -->
-
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="12dip"
-                    android:layout_marginStart="18dip"
-                    android:layout_marginEnd="24dip"
-                    android:textAppearance="@style/PrintOptionTitleTextAppearance"
-                    android:labelFor="@+id/paper_size_spinner"
-                    android:text="@string/label_paper_size">
-                </TextView>
-
-                <Spinner
-                    android:id="@+id/paper_size_spinner"
-                    android:layout_width="fill_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="6dip"
-                    android:layout_marginEnd="24dip"
-                    style="@style/PrintOptionSpinnerStyle">
-                </Spinner>
-
-                <!-- Orientation -->
-
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="12dip"
-                    android:layout_marginStart="18dip"
-                    android:layout_marginEnd="24dip"
-                    android:textAppearance="@style/PrintOptionTitleTextAppearance"
-                    android:labelFor="@+id/orientation_spinner"
-                    android:text="@string/label_orientation">
-                </TextView>
-
-                <Spinner
-                    android:id="@+id/orientation_spinner"
-                    android:layout_width="fill_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="6dip"
-                    android:layout_marginEnd="24dip"
-                    style="@style/PrintOptionSpinnerStyle">
-                </Spinner>
-
-                <!-- Pages -->
-
-               <TextView
-                    android:id="@+id/page_range_title"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="12dip"
-                    android:layout_marginStart="12dip"
-                    android:layout_marginEnd="24dip"
-                    android:textAppearance="@style/PrintOptionTitleTextAppearance"
-                    android:text="@string/pages_range_example"
-                    android:labelFor="@+id/page_range_edittext"
-                    android:textAllCaps="false">
-                </TextView>
-
-                <view
-                    class="com.android.printspooler.PrintJobConfigActivity$CustomEditText"
-                    android:id="@+id/page_range_edittext"
-                    android:layout_width="fill_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="6dip"
-                    android:layout_marginEnd="24dip"
-                    android:layout_gravity="bottom|fill_horizontal"
-                    style="@style/PrintOptionEditTextStyle"
-                    android:visibility="gone"
-                    android:inputType="textNoSuggestions">
-                </view>
-
-            </LinearLayout>
-
-        </LinearLayout>
-
-        <!-- Advanced settings button -->
-
-        <LinearLayout
-           android:id="@+id/advanced_settings_container"
-           android:layout_width="fill_parent"
-           android:layout_height="wrap_content"
-           android:orientation="vertical"
-           android:visibility="gone">
-
-            <ImageView
-                android:layout_width="fill_parent"
-                android:layout_height="1dip"
-                android:layout_marginStart="24dip"
-                android:layout_marginEnd="24dip"
-                android:layout_gravity="fill_horizontal"
-                android:background="@color/separator"
-                android:contentDescription="@null">
-            </ImageView>
-
-            <Button
-                android:id="@+id/advanced_settings_button"
-                style="?android:attr/buttonBarButtonStyle"
-                android:layout_width="fill_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginStart="24dip"
-                android:layout_marginEnd="24dip"
-                android:layout_gravity="fill_horizontal"
-                android:text="@string/advanced_settings_button"
-                android:gravity="start|center_vertical"
-                android:textSize="16sp"
-                android:textColor="@color/item_text_color">
-            </Button>
-
-            <ImageView
-                android:layout_width="fill_parent"
-                android:layout_height="1dip"
-                android:layout_gravity="fill_horizontal"
-                android:layout_marginStart="24dip"
-                android:layout_marginEnd="24dip"
-                android:background="@color/separator"
-                android:contentDescription="@null">
-            </ImageView>
-
-        </LinearLayout>
-
-        <!-- Print button -->
-
-        <FrameLayout
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="24dip"
-            android:background="@color/action_button_background">
-
-            <ImageView
-                android:layout_width="fill_parent"
-                android:layout_height="1dip"
-                android:layout_gravity="fill_horizontal"
-                android:background="@color/separator"
-                android:contentDescription="@null">
-            </ImageView>
-
-            <Button
-                android:id="@+id/print_button"
-                style="?android:attr/buttonBarButtonStyle"
-                android:layout_width="fill_parent"
-                android:layout_height="wrap_content"
-                android:layout_gravity="fill_horizontal"
-                android:text="@string/print_button"
-                android:textSize="16sp"
-                android:textColor="@color/item_text_color">
-            </Button>
-
-        </FrameLayout>
-
-    </LinearLayout>
-
-</ScrollView>
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml
deleted file mode 100644
index d9f0a9a..0000000
--- a/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml
+++ /dev/null
@@ -1,70 +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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/content_generating"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <LinearLayout
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
-        android:orientation="vertical">
-
-        <TextView
-            android:id="@+id/message"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dip"
-            android:layout_marginEnd="16dip"
-            android:layout_marginTop="32dip"
-            android:layout_marginBottom="32dip"
-            android:layout_gravity="center"
-            style="?android:attr/buttonBarButtonStyle"
-            android:ellipsize="end"
-            android:text="@string/print_error_default_message"
-            android:textColor="@color/important_text"
-            android:textSize="16sp">
-        </TextView>
-
-    </LinearLayout>
-
-    <FrameLayout
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:background="@color/action_button_background">
-
-        <View
-            android:layout_width="fill_parent"
-            android:layout_height="1dip"
-            android:background="@color/separator">
-        </View>
-
-        <Button
-            android:id="@+id/ok_button"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:layout_gravity="fill_horizontal"
-            style="?android:attr/buttonBarButtonStyle"
-            android:text="@android:string/ok"
-            android:textSize="16sp"
-            android:textColor="@color/important_text">
-        </Button>
-
-    </FrameLayout>
-
-</LinearLayout>
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml
deleted file mode 100644
index 10602ee..0000000
--- a/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml
+++ /dev/null
@@ -1,79 +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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/content_generating"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <LinearLayout
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
-        android:orientation="vertical">
-
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dip"
-            android:layout_marginEnd="16dip"
-            android:layout_gravity="center"
-            style="?android:attr/buttonBarButtonStyle"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:text="@string/generating_print_job"
-            android:textColor="@color/important_text"
-            android:textSize="16sp">
-        </TextView>
-
-        <ProgressBar
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="32dip"
-            android:layout_marginEnd="32dip"
-            android:layout_marginTop="16dip"
-            android:layout_marginBottom="32dip"
-            android:layout_gravity="center_horizontal"
-            style="?android:attr/progressBarStyleLarge">
-        </ProgressBar>
-
-    </LinearLayout>
-
-    <FrameLayout
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:background="@color/action_button_background">
-
-        <View
-            android:layout_width="fill_parent"
-            android:layout_height="1dip"
-            android:background="@color/separator">
-        </View>
-
-        <Button
-            android:id="@+id/cancel_button"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:layout_gravity="fill_horizontal"
-            style="?android:attr/buttonBarButtonStyle"
-            android:text="@string/cancel"
-            android:textSize="16sp"
-            android:textColor="@color/important_text">
-        </Button>
-
-    </FrameLayout>
-
-</LinearLayout>
diff --git a/packages/PrintSpooler/res/layout/print_progress_fragment.xml b/packages/PrintSpooler/res/layout/print_progress_fragment.xml
new file mode 100644
index 0000000..212da9e
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/print_progress_fragment.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="12dip"
+        android:src="@drawable/ic_grayedout_printer"
+        android:contentDescription="@null">
+    </ImageView>
+
+    <ProgressBar
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:indeterminate="true"
+        style="?android:attr/progressBarStyleHorizontal">
+    </ProgressBar>
+
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:minHeight="?android:attr/listPreferredItemHeight"
+        android:gravity="center"
+        android:animateLayoutChanges="true">
+
+        <TextView
+            android:id="@+id/message"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceLargeInverse"
+            android:text="@string/print_operation_canceling"
+            android:visibility="gone">
+        </TextView>
+
+        <Button
+            android:id="@+id/cancel_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@android:string/cancel">
+        </Button>
+
+    </FrameLayout>
+
+</LinearLayout>
+
diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
index 1a61b99..43d8aaf 100644
--- a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
+++ b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
@@ -19,7 +19,7 @@
       android:layout_height="wrap_content"
       android:paddingStart="16dip"
       android:paddingEnd="16dip"
-      android:minHeight="?android:attr/listPreferredItemHeightSmall"
+      android:minHeight="56dip"
       android:orientation="horizontal"
       android:gravity="start|center_vertical">
 
@@ -49,7 +49,7 @@
             android:ellipsize="end"
             android:textIsSelectable="false"
             android:gravity="top|start"
-            android:textColor="@color/item_text_color"
+            android:textColor="?android:attr/textColorPrimary"
             android:duplicateParentState="true">
         </TextView>
 
@@ -62,7 +62,7 @@
             android:ellipsize="end"
             android:textIsSelectable="false"
             android:visibility="gone"
-            android:textColor="@color/print_option_title"
+            android:textColor="?android:attr/textColorPrimary"
             android:duplicateParentState="true">
         </TextView>
 
diff --git a/packages/PrintSpooler/res/layout/printer_list_item.xml b/packages/PrintSpooler/res/layout/printer_list_item.xml
index 47eb0b5..1f5efbc 100644
--- a/packages/PrintSpooler/res/layout/printer_list_item.xml
+++ b/packages/PrintSpooler/res/layout/printer_list_item.xml
@@ -15,13 +15,13 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-        android:minHeight="?android:attr/listPreferredItemHeight"
-        android:orientation="horizontal"
-        android:gravity="start|center_vertical">
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:orientation="horizontal"
+    android:gravity="start|center_vertical">
 
     <ImageView
         android:id="@+id/icon"
@@ -31,7 +31,7 @@
         android:layout_marginEnd="8dip"
         android:duplicateParentState="true"
         android:contentDescription="@null"
-        android:visibility="gone">
+        android:visibility="invisible">
     </ImageView>
 
     <LinearLayout
@@ -49,7 +49,7 @@
             android:ellipsize="end"
             android:textIsSelectable="false"
             android:gravity="top|start"
-            android:textColor="@color/item_text_color"
+            android:textColor="?android:attr/textColorSecondary"
             android:duplicateParentState="true">
         </TextView>
 
@@ -62,7 +62,7 @@
             android:ellipsize="end"
             android:textIsSelectable="false"
             android:visibility="gone"
-            android:textColor="@color/print_option_title"
+            android:textColor="?android:attr/textColorSecondary"
             android:duplicateParentState="true">
         </TextView>
 
diff --git a/packages/PrintSpooler/res/layout/select_printer_activity.xml b/packages/PrintSpooler/res/layout/select_printer_activity.xml
index 4488b6a..173057b 100644
--- a/packages/PrintSpooler/res/layout/select_printer_activity.xml
+++ b/packages/PrintSpooler/res/layout/select_printer_activity.xml
@@ -19,12 +19,16 @@
     android:layout_width="fill_parent"
     android:layout_height="fill_parent">
 
-    <fragment
-        android:name="com.android.printspooler.SelectPrinterFragment"
-        android:id="@+id/select_printer_fragment"
+    <ListView
+        android:id="@android:id/list"
         android:layout_width="fill_parent"
-        android:layout_height="wrap_content">
-    </fragment>
+        android:layout_height="fill_parent"
+        android:paddingStart="@dimen/printer_list_view_padding_start"
+        android:paddingEnd="@dimen/printer_list_view_padding_end"
+        android:scrollbarStyle="outsideOverlay"
+        android:cacheColorHint="@android:color/transparent"
+        android:scrollbarAlwaysDrawVerticalTrack="true" >
+    </ListView>
 
     <FrameLayout
         android:id="@+id/empty_print_state"
diff --git a/packages/PrintSpooler/res/layout/select_printer_fragment.xml b/packages/PrintSpooler/res/layout/select_printer_fragment.xml
deleted file mode 100644
index bbd012e..0000000
--- a/packages/PrintSpooler/res/layout/select_printer_fragment.xml
+++ /dev/null
@@ -1,26 +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.
--->
-
-<ListView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/list"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    android:paddingStart="@dimen/printer_list_view_padding_start"
-    android:paddingEnd="@dimen/printer_list_view_padding_end"
-    android:scrollbarStyle="outsideOverlay"
-    android:cacheColorHint="@android:color/transparent"
-    android:scrollbarAlwaysDrawVerticalTrack="true" >
-</ListView>
diff --git a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
index c3c5021..1fb221a 100644
--- a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
+++ b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
@@ -32,7 +32,7 @@
         android:ellipsize="end"
         android:textIsSelectable="false"
         android:gravity="top|left"
-        android:textColor="@color/item_text_color"
+        android:textColor="?android:attr/textColorPrimary"
         android:duplicateParentState="true">
     </TextView>
 
@@ -45,7 +45,7 @@
         android:ellipsize="end"
         android:textIsSelectable="false"
         android:visibility="gone"
-        android:textColor="@color/print_option_title"
+        android:textColor="?android:attr/textColorPrimary"
         android:duplicateParentState="true">
     </TextView>
 
diff --git a/packages/PrintSpooler/res/values-af/strings.xml b/packages/PrintSpooler/res/values-af/strings.xml
index a10a5a0..876dc01 100644
--- a/packages/PrintSpooler/res/values-af/strings.xml
+++ b/packages/PrintSpooler/res/values-af/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Drukwaglys"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Drukkerinstellings"</string>
-    <string name="print_button" msgid="645164566271246268">"Druk"</string>
-    <string name="save_button" msgid="1921310454071758999">"Stoor"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Bestemming"</string>
     <string name="label_copies" msgid="3634531042822968308">"Afskrifte"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Papiergrootte"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Kleur"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Oriëntasie"</string>
     <string name="label_pages" msgid="6300874667546617333">"Bladsye (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Geen verbinding met drukker nie"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"onbekend"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – nie beskikbaar nie"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Kon nie uitdruktaak genereer nie"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Swart en wit"</item>
     <item msgid="2762241247228983754">"Kleur"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Alles"</item>
     <item msgid="6812869625222503603">"Reikwydte"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Kon nie uitdruktaak genereer nie"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-am/strings.xml b/packages/PrintSpooler/res/values-am/strings.xml
index be64c95..b7f1d7f 100644
--- a/packages/PrintSpooler/res/values-am/strings.xml
+++ b/packages/PrintSpooler/res/values-am/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"የህትመት አስተላላፊ"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"የአታሚ ቅንብሮች"</string>
-    <string name="print_button" msgid="645164566271246268">"አትም"</string>
-    <string name="save_button" msgid="1921310454071758999">"አስቀምጥ"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"መድረሻ"</string>
     <string name="label_copies" msgid="3634531042822968308">"ቅጂዎች"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"የወረቀት መጠን"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"ቀለም"</string>
     <string name="label_orientation" msgid="2853142581990496477">"አቀማመጠ ገፅ"</string>
     <string name="label_pages" msgid="6300874667546617333">"ገጾች (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"ከአታሚ ጋር ምንም ግንኙነት የለም"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"አይታወቅም"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – አይገኝም"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"የህትመት ስራን ማመንጨት አልተቻለም"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"ጥቁር እና ነጭ"</item>
     <item msgid="2762241247228983754">"ቀለም"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"ሁሉም"</item>
     <item msgid="6812869625222503603">"ምጥጥነ ገጽታ"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"የህትመት ስራን ማመንጨት አልተቻለም"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-ar/strings.xml b/packages/PrintSpooler/res/values-ar/strings.xml
index b883f93..ca6100b 100644
--- a/packages/PrintSpooler/res/values-ar/strings.xml
+++ b/packages/PrintSpooler/res/values-ar/strings.xml
@@ -17,12 +17,12 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"إعدادات الطابعة"</string>
-    <string name="print_button" msgid="645164566271246268">"طباعة"</string>
-    <string name="save_button" msgid="1921310454071758999">"حفظ"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"خيارات أخرى"</string>
     <string name="label_destination" msgid="9132510997381599275">"الوجهة"</string>
     <string name="label_copies" msgid="3634531042822968308">"عدد النسخ"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"حجم الورق"</string>
+    <string name="label_copies_summary" msgid="3861966063536529540">"النُسخ:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"حجم الورق"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"حجم الورق:"</string>
     <string name="label_color" msgid="1108690305218188969">"ألوان"</string>
     <string name="label_orientation" msgid="2853142581990496477">"الاتجاه"</string>
     <string name="label_pages" msgid="6300874667546617333">"الصفحات (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +63,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"لا يوجد اتصال بالطابعة"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"غير معروف"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – غير متاحة"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"تعذر إنشاء عملية الطباعة"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"أبيض وأسود"</item>
     <item msgid="2762241247228983754">"ملونة"</item>
@@ -76,4 +75,9 @@
     <item msgid="7421377442011699994">"الكل"</item>
     <item msgid="6812869625222503603">"النطاق"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"تعذرت الكتابة إلى الملف"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"تعذر إنشاء عملية الطباعة"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"إعادة المحاولة"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"الطابعة غير متاحة"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"جارٍ الإلغاء…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-bg/strings.xml b/packages/PrintSpooler/res/values-bg/strings.xml
index 4009aa2..b1a41e6 100644
--- a/packages/PrintSpooler/res/values-bg/strings.xml
+++ b/packages/PrintSpooler/res/values-bg/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Спулер за печат"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Настройки на принтера"</string>
-    <string name="print_button" msgid="645164566271246268">"Печат"</string>
-    <string name="save_button" msgid="1921310454071758999">"Запазване"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Местоназначение"</string>
     <string name="label_copies" msgid="3634531042822968308">"Копия"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Размер на хартията"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Цвят"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Ориентация"</string>
     <string name="label_pages" msgid="6300874667546617333">"Страници (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Няма връзка с принтера"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"няма данни"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – не е налице"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Заданието за отпечатване не можа да се генерира"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Черно-бяло"</item>
     <item msgid="2762241247228983754">"Цветно"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Всички"</item>
     <item msgid="6812869625222503603">"Поредица"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Заданието за отпечатване не можа да се генерира"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-ca/strings.xml b/packages/PrintSpooler/res/values-ca/strings.xml
index a429dd5..c553a96 100644
--- a/packages/PrintSpooler/res/values-ca/strings.xml
+++ b/packages/PrintSpooler/res/values-ca/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Gest. cues impr."</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Configuració impressora"</string>
-    <string name="print_button" msgid="645164566271246268">"Imprimeix"</string>
-    <string name="save_button" msgid="1921310454071758999">"Desa"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Destinació"</string>
     <string name="label_copies" msgid="3634531042822968308">"Còpies"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Mida del paper"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Color"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientació"</string>
     <string name="label_pages" msgid="6300874667546617333">"Pàgines (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"No hi ha connexió amb la impressora"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"desconegut"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>: no disponible"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"No s\'ha pogut generar la tasca d\'impressió"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Blanc i negre"</item>
     <item msgid="2762241247228983754">"Color"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Tots"</item>
     <item msgid="6812869625222503603">"Interval"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"No s\'ha pogut generar la tasca d\'impressió"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-cs/strings.xml b/packages/PrintSpooler/res/values-cs/strings.xml
index 3ddc701..aff3e95 100644
--- a/packages/PrintSpooler/res/values-cs/strings.xml
+++ b/packages/PrintSpooler/res/values-cs/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Nastavení tiskárny"</string>
-    <string name="print_button" msgid="645164566271246268">"Tisk"</string>
-    <string name="save_button" msgid="1921310454071758999">"Uložit"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Cíl"</string>
     <string name="label_copies" msgid="3634531042822968308">"Kopie"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Velikost papíru"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Barva"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientace"</string>
     <string name="label_pages" msgid="6300874667546617333">"STRÁNKY (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nelze se připojit k tiskárně"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"neznámé"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – není k dispozici"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Tiskovou úlohu nelze vytvořit"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Černobíle"</item>
     <item msgid="2762241247228983754">"Barevně"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Vše"</item>
     <item msgid="6812869625222503603">"Rozsah"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tiskovou úlohu nelze vytvořit"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-da/strings.xml b/packages/PrintSpooler/res/values-da/strings.xml
index 1a871f8..54c9f22 100644
--- a/packages/PrintSpooler/res/values-da/strings.xml
+++ b/packages/PrintSpooler/res/values-da/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Printerindstillinger"</string>
-    <string name="print_button" msgid="645164566271246268">"Udskriv"</string>
-    <string name="save_button" msgid="1921310454071758999">"Gem"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Destination"</string>
     <string name="label_copies" msgid="3634531042822968308">"Kopier"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Papirstørrelse"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Farve"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Retning"</string>
     <string name="label_pages" msgid="6300874667546617333">"Sider (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Ingen forbindelse til printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ukendt"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ikke tilgængelig"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Der kunne ikke genereres et udskriftsjob"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Sort/hvid"</item>
     <item msgid="2762241247228983754">"Farve"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Alle"</item>
     <item msgid="6812869625222503603">"Interval"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Der kunne ikke genereres et udskriftsjob"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-de/strings.xml b/packages/PrintSpooler/res/values-de/strings.xml
index 6b83ac3..321c709 100644
--- a/packages/PrintSpooler/res/values-de/strings.xml
+++ b/packages/PrintSpooler/res/values-de/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Druck-Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Druckereinstellungen"</string>
-    <string name="print_button" msgid="645164566271246268">"Drucken"</string>
-    <string name="save_button" msgid="1921310454071758999">"Speichern"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Ziel"</string>
     <string name="label_copies" msgid="3634531042822968308">"Exemplare"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Papiergröße"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Farbe"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Ausrichtung"</string>
     <string name="label_pages" msgid="6300874667546617333">"Seiten (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Keine Verbindung zum Drucker"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"unbekannt"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – nicht verfügbar"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Druckauftrag konnte nicht generiert werden."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Schwarz-weiß"</item>
     <item msgid="2762241247228983754">"Farbe"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Alle"</item>
     <item msgid="6812869625222503603">"Bereich"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Druckauftrag konnte nicht generiert werden."</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-el/strings.xml b/packages/PrintSpooler/res/values-el/strings.xml
index 795e730..542caf9 100644
--- a/packages/PrintSpooler/res/values-el/strings.xml
+++ b/packages/PrintSpooler/res/values-el/strings.xml
@@ -17,12 +17,12 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Λογισμικό ουράς εκτύπωσης"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Ρυθμίσεις εκτυπωτή"</string>
-    <string name="print_button" msgid="645164566271246268">"Εκτύπωση"</string>
-    <string name="save_button" msgid="1921310454071758999">"Αποθήκευση"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"Περισσότερες επιλογές"</string>
     <string name="label_destination" msgid="9132510997381599275">"Προορισμός"</string>
     <string name="label_copies" msgid="3634531042822968308">"Αντίγραφα"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Μέγεθος χαρτιού"</string>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Αντίγραφα:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Μεγέθος χαρτιού"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Μέγεθος χαρτιού:"</string>
     <string name="label_color" msgid="1108690305218188969">"Χρώμα"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Προσανατολισμός"</string>
     <string name="label_pages" msgid="6300874667546617333">"Σελίδες (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +63,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Δεν υπάρχει σύνδεση με εκτυπωτή"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"άγνωστο"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – μη διαθέσιμο"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Δεν ήταν δυνατή η δημιουργία εργασίας εκτύπωσης"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Ασπρόμαυρο"</item>
     <item msgid="2762241247228983754">"Χρώμα"</item>
@@ -76,4 +75,9 @@
     <item msgid="7421377442011699994">"Όλα"</item>
     <item msgid="6812869625222503603">"Εύρος"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Δεν ήταν δυνατή η εγγραφή στο αρχείο"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Δεν ήταν δυνατή η δημιουργία εργασίας εκτύπωσης"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Επανάληψη"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Ο εκτυπωτής δεν είναι διαθέσιμος"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Ακύρωση…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-en-rGB/strings.xml b/packages/PrintSpooler/res/values-en-rGB/strings.xml
index 27372f8..3728437 100644
--- a/packages/PrintSpooler/res/values-en-rGB/strings.xml
+++ b/packages/PrintSpooler/res/values-en-rGB/strings.xml
@@ -17,12 +17,12 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Printer settings"</string>
-    <string name="print_button" msgid="645164566271246268">"Print"</string>
-    <string name="save_button" msgid="1921310454071758999">"Save"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"More options"</string>
     <string name="label_destination" msgid="9132510997381599275">"Destination"</string>
     <string name="label_copies" msgid="3634531042822968308">"Copies"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Paper Size"</string>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Copies:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Paper size"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Paper size:"</string>
     <string name="label_color" msgid="1108690305218188969">"Colour"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientation"</string>
     <string name="label_pages" msgid="6300874667546617333">"Pages (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +63,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"No connection to printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"unknown"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – unavailable"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Couldn\'t generate print job"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Black &amp; White"</item>
     <item msgid="2762241247228983754">"Colour"</item>
@@ -76,4 +75,9 @@
     <item msgid="7421377442011699994">"All"</item>
     <item msgid="6812869625222503603">"Range"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Couldn\'t write to file"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Couldn\'t generate print job"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Retry"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Printer unavailable"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Cancelling…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-en-rIN/strings.xml b/packages/PrintSpooler/res/values-en-rIN/strings.xml
index 27372f8..3728437 100644
--- a/packages/PrintSpooler/res/values-en-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-en-rIN/strings.xml
@@ -17,12 +17,12 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Printer settings"</string>
-    <string name="print_button" msgid="645164566271246268">"Print"</string>
-    <string name="save_button" msgid="1921310454071758999">"Save"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"More options"</string>
     <string name="label_destination" msgid="9132510997381599275">"Destination"</string>
     <string name="label_copies" msgid="3634531042822968308">"Copies"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Paper Size"</string>
+    <string name="label_copies_summary" msgid="3861966063536529540">"Copies:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"Paper size"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"Paper size:"</string>
     <string name="label_color" msgid="1108690305218188969">"Colour"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientation"</string>
     <string name="label_pages" msgid="6300874667546617333">"Pages (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +63,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"No connection to printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"unknown"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – unavailable"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Couldn\'t generate print job"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Black &amp; White"</item>
     <item msgid="2762241247228983754">"Colour"</item>
@@ -76,4 +75,9 @@
     <item msgid="7421377442011699994">"All"</item>
     <item msgid="6812869625222503603">"Range"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"Couldn\'t write to file"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Couldn\'t generate print job"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"Retry"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"Printer unavailable"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"Cancelling…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-es-rUS/strings.xml b/packages/PrintSpooler/res/values-es-rUS/strings.xml
index c7d12af..8050ea5 100644
--- a/packages/PrintSpooler/res/values-es-rUS/strings.xml
+++ b/packages/PrintSpooler/res/values-es-rUS/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Cola de impresión"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Config. de impresora"</string>
-    <string name="print_button" msgid="645164566271246268">"Imprimir"</string>
-    <string name="save_button" msgid="1921310454071758999">"Guardar"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Destino"</string>
     <string name="label_copies" msgid="3634531042822968308">"Copias"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Tamaño del papel"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Color"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientación"</string>
     <string name="label_pages" msgid="6300874667546617333">"Páginas (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"No hay conexión con la impresora."</string>
     <string name="reason_unknown" msgid="5507940196503246139">"desconocido"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>: no disponible"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Error al generar el trabajo de impresión"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Blanco y negro"</item>
     <item msgid="2762241247228983754">"Color"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Todas"</item>
     <item msgid="6812869625222503603">"Intervalo"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Error al generar el trabajo de impresión"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-es/strings.xml b/packages/PrintSpooler/res/values-es/strings.xml
index 0225cad..f8bf6a4 100644
--- a/packages/PrintSpooler/res/values-es/strings.xml
+++ b/packages/PrintSpooler/res/values-es/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Cola de impresión"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Ajustes de impresora"</string>
-    <string name="print_button" msgid="645164566271246268">"Imprimir"</string>
-    <string name="save_button" msgid="1921310454071758999">"Guardar"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Destino"</string>
     <string name="label_copies" msgid="3634531042822968308">"Copias"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Tamaño del papel"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Color"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientación"</string>
     <string name="label_pages" msgid="6300874667546617333">"Páginas (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"No hay conexión con la impresora"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"desconocido"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – no disponible"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Error al generar el trabajo de impresión"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Blanco y negro"</item>
     <item msgid="2762241247228983754">"Color"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Todo"</item>
     <item msgid="6812869625222503603">"Intervalo"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Error al generar el trabajo de impresión"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-et-rEE/strings.xml b/packages/PrintSpooler/res/values-et-rEE/strings.xml
index 2b3b352..301be4c 100644
--- a/packages/PrintSpooler/res/values-et-rEE/strings.xml
+++ b/packages/PrintSpooler/res/values-et-rEE/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Prindispuuler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Printeri seaded"</string>
-    <string name="print_button" msgid="645164566271246268">"Prindi"</string>
-    <string name="save_button" msgid="1921310454071758999">"Salvesta"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Sihtkoht"</string>
     <string name="label_copies" msgid="3634531042822968308">"Koopiaid"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Paberiformaat"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Värv"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Suund"</string>
     <string name="label_pages" msgid="6300874667546617333">"Lehti (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Printeriühendus puudub"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"teadmata"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – pole saadaval"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Prinditööd ei saanud luua"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Mustvalge"</item>
     <item msgid="2762241247228983754">"Värv"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Kõik"</item>
     <item msgid="6812869625222503603">"Vahemik"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Prinditööd ei saanud luua"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-fa/strings.xml b/packages/PrintSpooler/res/values-fa/strings.xml
index 49bae323..9aa0aeb 100644
--- a/packages/PrintSpooler/res/values-fa/strings.xml
+++ b/packages/PrintSpooler/res/values-fa/strings.xml
@@ -17,12 +17,12 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"هماهنگ‌کننده چاپ"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"تنظیمات چاپگر"</string>
-    <string name="print_button" msgid="645164566271246268">"چاپ"</string>
-    <string name="save_button" msgid="1921310454071758999">"ذخیره"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"گزینه‌های بیشتر"</string>
     <string name="label_destination" msgid="9132510997381599275">"مقصد"</string>
     <string name="label_copies" msgid="3634531042822968308">"کپی‌ها"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"اندازه کاغذ"</string>
+    <string name="label_copies_summary" msgid="3861966063536529540">"تعداد نسخه‌ها:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"اندازه کاغذ"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"اندازه کاغذ:"</string>
     <string name="label_color" msgid="1108690305218188969">"رنگی"</string>
     <string name="label_orientation" msgid="2853142581990496477">"جهت"</string>
     <string name="label_pages" msgid="6300874667546617333">"صفحات (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +63,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"اتصال با چاپگر برقرار نیست"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"نامعلوم"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - در دسترس نیست"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"کار چاپ ایجاد نشد"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"سیاه و سفید"</item>
     <item msgid="2762241247228983754">"رنگی"</item>
@@ -76,4 +75,9 @@
     <item msgid="7421377442011699994">"همه"</item>
     <item msgid="6812869625222503603">"محدوده"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"در فایل نوشته نشد"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"کار چاپ ایجاد نشد"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"امتحان مجدد"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"چاپگر در دسترس نیست"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"در حال لغو…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-fi/strings.xml b/packages/PrintSpooler/res/values-fi/strings.xml
index 8658e04..8dcaa9c 100644
--- a/packages/PrintSpooler/res/values-fi/strings.xml
+++ b/packages/PrintSpooler/res/values-fi/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Taustatulostus"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Tulostimen asetukset"</string>
-    <string name="print_button" msgid="645164566271246268">"Tulosta"</string>
-    <string name="save_button" msgid="1921310454071758999">"Tallenna"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Kohde"</string>
     <string name="label_copies" msgid="3634531042822968308">"Kopiot"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Paperikoko"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Väri"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Suunta"</string>
     <string name="label_pages" msgid="6300874667546617333">"Sivut (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Ei yhteyttä tulostimeen"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"tuntematon"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ei käytettävissä"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Tulostustyötä ei voitu luoda"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Mustavalkoinen"</item>
     <item msgid="2762241247228983754">"Väri"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Kaikki"</item>
     <item msgid="6812869625222503603">"Väli"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tulostustyötä ei voitu luoda"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-fr-rCA/strings.xml b/packages/PrintSpooler/res/values-fr-rCA/strings.xml
index 9a3352c..b37c8ac 100644
--- a/packages/PrintSpooler/res/values-fr-rCA/strings.xml
+++ b/packages/PrintSpooler/res/values-fr-rCA/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"File d\'att. impr."</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Paramètres de l\'imprimante"</string>
-    <string name="print_button" msgid="645164566271246268">"Imprimer"</string>
-    <string name="save_button" msgid="1921310454071758999">"Enregistrer"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Destination"</string>
     <string name="label_copies" msgid="3634531042822968308">"Copies"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Format du papier"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Couleur"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientation"</string>
     <string name="label_pages" msgid="6300874667546617333">"Pages (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Aucune connexion à l\'imprimante"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"inconnu"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> — indisponible"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Impossible de générer la tâche d\'impression"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Noir et blanc"</item>
     <item msgid="2762241247228983754">"Couleur"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Tous"</item>
     <item msgid="6812869625222503603">"Plage"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Impossible de générer la tâche d\'impression"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-fr/strings.xml b/packages/PrintSpooler/res/values-fr/strings.xml
index 17fabdc..971039b 100644
--- a/packages/PrintSpooler/res/values-fr/strings.xml
+++ b/packages/PrintSpooler/res/values-fr/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Spouler impress."</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Paramètres de l\'imprimante"</string>
-    <string name="print_button" msgid="645164566271246268">"Imprimer"</string>
-    <string name="save_button" msgid="1921310454071758999">"Enregistrer"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Destination"</string>
     <string name="label_copies" msgid="3634531042822968308">"Copies"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Format du papier"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Couleur"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientation"</string>
     <string name="label_pages" msgid="6300874667546617333">"Pages (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Aucune connexion à l\'imprimante."</string>
     <string name="reason_unknown" msgid="5507940196503246139">"inconnue"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – indisponible"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Impossible de générer la tâche d\'impression."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Noir et blanc"</item>
     <item msgid="2762241247228983754">"Couleur"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Tout"</item>
     <item msgid="6812869625222503603">"Plage"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Impossible de générer la tâche d\'impression."</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-hi/strings.xml b/packages/PrintSpooler/res/values-hi/strings.xml
index 60406b7..c46dff8 100644
--- a/packages/PrintSpooler/res/values-hi/strings.xml
+++ b/packages/PrintSpooler/res/values-hi/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"प्रिंट स्पूलर"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"प्रिंटर सेटिंग"</string>
-    <string name="print_button" msgid="645164566271246268">"प्रिंट करें"</string>
-    <string name="save_button" msgid="1921310454071758999">"सहेजें"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"गंतव्य"</string>
     <string name="label_copies" msgid="3634531042822968308">"प्रतियां"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"कागज़ का आकार"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"रंग"</string>
     <string name="label_orientation" msgid="2853142581990496477">"अभिविन्‍यास"</string>
     <string name="label_pages" msgid="6300874667546617333">"पृष्‍ठ (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"प्रिंटर के लिए कोई कनेक्शन नहीं"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"अज्ञात"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – अनुपलब्ध"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"प्रिंट कार्य जनरेट नहीं किया जा सका"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"श्याम और श्वेत"</item>
     <item msgid="2762241247228983754">"रंग"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"सभी"</item>
     <item msgid="6812869625222503603">"सीमा"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"प्रिंट कार्य जनरेट नहीं किया जा सका"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-hr/strings.xml b/packages/PrintSpooler/res/values-hr/strings.xml
index 7182cf6..51e750a 100644
--- a/packages/PrintSpooler/res/values-hr/strings.xml
+++ b/packages/PrintSpooler/res/values-hr/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Postavke pisača"</string>
-    <string name="print_button" msgid="645164566271246268">"Ispis"</string>
-    <string name="save_button" msgid="1921310454071758999">"Spremi"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Odredište"</string>
     <string name="label_copies" msgid="3634531042822968308">"Kopije"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Veličina papira"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"U boji"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orijentacija"</string>
     <string name="label_pages" msgid="6300874667546617333">"Stranice (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nema veze s pisačem"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"nepoznato"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – zadatak nije dostupan"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Zadatak ispisa nije generiran"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Crno-bijelo"</item>
     <item msgid="2762241247228983754">"U boji"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Sve"</item>
     <item msgid="6812869625222503603">"Raspon"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Zadatak ispisa nije generiran"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-hu/strings.xml b/packages/PrintSpooler/res/values-hu/strings.xml
index 6a0741b..fb193f0 100644
--- a/packages/PrintSpooler/res/values-hu/strings.xml
+++ b/packages/PrintSpooler/res/values-hu/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Nyomtatásisor-kezelő"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Nyomtatóbeállítások"</string>
-    <string name="print_button" msgid="645164566271246268">"Nyomtatás"</string>
-    <string name="save_button" msgid="1921310454071758999">"Mentés"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Cél"</string>
     <string name="label_copies" msgid="3634531042822968308">"Példányszám"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Papírméret"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Szín"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Tájolás"</string>
     <string name="label_pages" msgid="6300874667546617333">"Oldalszám (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nincs kapcsolat a nyomtatóval"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ismeretlen"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – nem érhető el"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Nem sikerült létrehozni a nyomtatási feladatot."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Fekete-fehér"</item>
     <item msgid="2762241247228983754">"Szín"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Összes"</item>
     <item msgid="6812869625222503603">"Tartomány"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Nem sikerült létrehozni a nyomtatási feladatot."</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-hy-rAM/strings.xml b/packages/PrintSpooler/res/values-hy-rAM/strings.xml
index 1423b82..1f1891b 100644
--- a/packages/PrintSpooler/res/values-hy-rAM/strings.xml
+++ b/packages/PrintSpooler/res/values-hy-rAM/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Տպման կարգավար"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Տպիչի կարգավորումներ"</string>
-    <string name="print_button" msgid="645164566271246268">"Տպել"</string>
-    <string name="save_button" msgid="1921310454071758999">"Պահել"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Նպատակակետ"</string>
     <string name="label_copies" msgid="3634531042822968308">"Պատճեններ"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Թղթի չափը"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Գույնը"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Դիրքավորում"</string>
     <string name="label_pages" msgid="6300874667546617333">"Էջեր (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Տպիչի հետ կապ չկա"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"անհայտ"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> տպիչն անհասանելի է"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Չկարողացանք մշակել տպման աշխատանքը"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Սև ու սպիտակ"</item>
     <item msgid="2762241247228983754">"Գույնը"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Բոլորը"</item>
     <item msgid="6812869625222503603">"Միջակայք"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Չկարողացանք մշակել տպման աշխատանքը"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-in/strings.xml b/packages/PrintSpooler/res/values-in/strings.xml
index 2b80d07..d5bc1f6 100644
--- a/packages/PrintSpooler/res/values-in/strings.xml
+++ b/packages/PrintSpooler/res/values-in/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Setelan printer"</string>
-    <string name="print_button" msgid="645164566271246268">"Cetak"</string>
-    <string name="save_button" msgid="1921310454071758999">"Simpan"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Tujuan"</string>
     <string name="label_copies" msgid="3634531042822968308">"Salinan"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Ukuran Kertas"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Warna"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientasi"</string>
     <string name="label_pages" msgid="6300874667546617333">"(<xliff:g id="PAGE_COUNT">%1$s</xliff:g>) halaman"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Tidak ada sambungan ke printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"tak diketahui"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – tidak tersedia"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Tidak dapat membuat tugas cetak"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Hitam &amp; Putih"</item>
     <item msgid="2762241247228983754">"Warna"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Semua"</item>
     <item msgid="6812869625222503603">"Rentang"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tidak dapat membuat tugas cetak"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-it/strings.xml b/packages/PrintSpooler/res/values-it/strings.xml
index 96c3b48..0f9201e 100644
--- a/packages/PrintSpooler/res/values-it/strings.xml
+++ b/packages/PrintSpooler/res/values-it/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Impostazioni stampante"</string>
-    <string name="print_button" msgid="645164566271246268">"Stampa"</string>
-    <string name="save_button" msgid="1921310454071758999">"Salva"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Destinazione"</string>
     <string name="label_copies" msgid="3634531042822968308">"Copie"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Formato carta"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"A colori"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientamento"</string>
     <string name="label_pages" msgid="6300874667546617333">"Pagine (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nessun collegamento alla stampante"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"sconosciuto"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - non disponibile"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Impossibile generare processo di stampa"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Bianco e nero"</item>
     <item msgid="2762241247228983754">"A colori"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Tutte"</item>
     <item msgid="6812869625222503603">"Intervallo"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Impossibile generare processo di stampa"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-iw/strings.xml b/packages/PrintSpooler/res/values-iw/strings.xml
index 3e0e732..e69221a 100644
--- a/packages/PrintSpooler/res/values-iw/strings.xml
+++ b/packages/PrintSpooler/res/values-iw/strings.xml
@@ -17,12 +17,12 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"הגדרות מדפסת"</string>
-    <string name="print_button" msgid="645164566271246268">"הדפס"</string>
-    <string name="save_button" msgid="1921310454071758999">"שמור"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"עוד אפשרויות"</string>
     <string name="label_destination" msgid="9132510997381599275">"יעד"</string>
     <string name="label_copies" msgid="3634531042822968308">"עותקים"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"גודל נייר"</string>
+    <string name="label_copies_summary" msgid="3861966063536529540">"עותקים:"</string>
+    <string name="label_paper_size" msgid="908654383827777759">"גודל נייר"</string>
+    <string name="label_paper_size_summary" msgid="5668204981332138168">"גודל נייר:"</string>
     <string name="label_color" msgid="1108690305218188969">"צבע"</string>
     <string name="label_orientation" msgid="2853142581990496477">"כיוון"</string>
     <string name="label_pages" msgid="6300874667546617333">"עמודים (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +63,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"אין חיבור למדפסת"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"לא ידוע"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – לא זמינה"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"לא ניתן היה ליצור את עבודת ההדפסה"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"שחור ולבן"</item>
     <item msgid="2762241247228983754">"צבע"</item>
@@ -76,4 +75,9 @@
     <item msgid="7421377442011699994">"הכל"</item>
     <item msgid="6812869625222503603">"טווח"</item>
   </string-array>
+    <string name="print_write_error_message" msgid="5787642615179572543">"לא ניתן היה לכתוב לקובץ"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"לא ניתן היה ליצור את עבודת ההדפסה"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"נסה שוב"</string>
+    <string name="print_error_printer_unavailable" msgid="6653128543854282851">"המדפסת אינה זמינה"</string>
+    <string name="print_operation_canceling" msgid="5274571823242489160">"מבטל…"</string>
 </resources>
diff --git a/packages/PrintSpooler/res/values-ja/strings.xml b/packages/PrintSpooler/res/values-ja/strings.xml
index d3f4f85..f01a157 100644
--- a/packages/PrintSpooler/res/values-ja/strings.xml
+++ b/packages/PrintSpooler/res/values-ja/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"印刷スプーラ"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"プリンタの設定"</string>
-    <string name="print_button" msgid="645164566271246268">"印刷"</string>
-    <string name="save_button" msgid="1921310454071758999">"保存"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"印刷先"</string>
     <string name="label_copies" msgid="3634531042822968308">"部数"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"用紙サイズ"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"カラー選択"</string>
     <string name="label_orientation" msgid="2853142581990496477">"方向"</string>
     <string name="label_pages" msgid="6300874667546617333">"ページ(<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"プリンタに接続されていません"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"不明"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>–使用不可"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"印刷ジョブを生成できませんでした"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"モノクロ"</item>
     <item msgid="2762241247228983754">"カラー"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"すべて"</item>
     <item msgid="6812869625222503603">"範囲"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"印刷ジョブを生成できませんでした"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-ka-rGE/strings.xml b/packages/PrintSpooler/res/values-ka-rGE/strings.xml
index d36b7c9..c645a7d 100644
--- a/packages/PrintSpooler/res/values-ka-rGE/strings.xml
+++ b/packages/PrintSpooler/res/values-ka-rGE/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"ბეჭდვის Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"პრინტერის პარამეტრები"</string>
-    <string name="print_button" msgid="645164566271246268">"ბეჭდვა"</string>
-    <string name="save_button" msgid="1921310454071758999">"შენახვა"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"დანიშნულება"</string>
     <string name="label_copies" msgid="3634531042822968308">"ასლები"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"ფურცლის ზომა"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"ფერი"</string>
     <string name="label_orientation" msgid="2853142581990496477">"ორიენტაცია"</string>
     <string name="label_pages" msgid="6300874667546617333">"გვერდები (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"პრინტერთან კავშირი არ არის"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"უცნობი"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – მიუწვდომელია"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"ბეჭდვის დავალების გენერაცია ვერ ხერხდება"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"შავ-თეთრი"</item>
     <item msgid="2762241247228983754">"ფერი"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"ყველა"</item>
     <item msgid="6812869625222503603">"დიაპაზონი"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"ბეჭდვის დავალების გენერაცია ვერ ხერხდება"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-km-rKH/strings.xml b/packages/PrintSpooler/res/values-km-rKH/strings.xml
index ba3c042..a456314 100644
--- a/packages/PrintSpooler/res/values-km-rKH/strings.xml
+++ b/packages/PrintSpooler/res/values-km-rKH/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"បោះពុម្ព​ស្ពូល័រ"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"ការ​កំណត់​ម៉ាស៊ីន​បោះពុម្ព"</string>
-    <string name="print_button" msgid="645164566271246268">"បោះពុម្ព"</string>
-    <string name="save_button" msgid="1921310454071758999">"រក្សាទុក"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"ទិសដៅ"</string>
     <string name="label_copies" msgid="3634531042822968308">"ច្បាប់​ចម្លង"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"ទំហំ​ក្រដាស"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"ពណ៌"</string>
     <string name="label_orientation" msgid="2853142581990496477">"ទិស"</string>
     <string name="label_pages" msgid="6300874667546617333">"ទំព័រ (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"គ្មាន​​​ការ​ភ្ជាប់​ទៅ​ម៉ាស៊ីន​បោះពុម្ព​"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"មិន​ស្គាល់"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – មិន​អាច​ប្រើ​បាន"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"មិន​អាច​បង្កើត​ការ​ងារ​បោះពុម្ព"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"ស &amp; ខ្មៅ"</item>
     <item msgid="2762241247228983754">"ពណ៌"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"ទាំង​អស់"</item>
     <item msgid="6812869625222503603">"ជួរ"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"មិន​អាច​បង្កើត​ការ​ងារ​បោះពុម្ព"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-ko/strings.xml b/packages/PrintSpooler/res/values-ko/strings.xml
index 3b2fef7..c1ace69 100644
--- a/packages/PrintSpooler/res/values-ko/strings.xml
+++ b/packages/PrintSpooler/res/values-ko/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"인쇄 스풀러"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"프린터 설정"</string>
-    <string name="print_button" msgid="645164566271246268">"인쇄"</string>
-    <string name="save_button" msgid="1921310454071758999">"저장"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"대상"</string>
     <string name="label_copies" msgid="3634531042822968308">"매수"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"용지 크기"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"색상"</string>
     <string name="label_orientation" msgid="2853142581990496477">"방향"</string>
     <string name="label_pages" msgid="6300874667546617333">"페이지 수(<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"프린터와 연결되지 않음"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"알 수 없음"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – 사용할 수 없음"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"인쇄 작업을 생성할 수 없습니다."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"흑백"</item>
     <item msgid="2762241247228983754">"컬러"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"모두"</item>
     <item msgid="6812869625222503603">"범위"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"인쇄 작업을 생성할 수 없습니다."</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-land/constants.xml b/packages/PrintSpooler/res/values-land/constants.xml
index d68b77e..0db7513 100644
--- a/packages/PrintSpooler/res/values-land/constants.xml
+++ b/packages/PrintSpooler/res/values-land/constants.xml
@@ -18,5 +18,6 @@
 
     <dimen name="printer_list_view_padding_start">48dip</dimen>
     <dimen name="printer_list_view_padding_end">48dip</dimen>
+    <integer name="print_option_column_count">3</integer>
 
 </resources>
diff --git a/packages/PrintSpooler/res/values-lo-rLA/strings.xml b/packages/PrintSpooler/res/values-lo-rLA/strings.xml
index f954606..c8c6caa 100644
--- a/packages/PrintSpooler/res/values-lo-rLA/strings.xml
+++ b/packages/PrintSpooler/res/values-lo-rLA/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"ຕົວຈັດຄິວການພິມ"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"ການຕັ້ງຄ່າເຄື່ອງພິມ"</string>
-    <string name="print_button" msgid="645164566271246268">"ພິມ"</string>
-    <string name="save_button" msgid="1921310454071758999">"ບັນທຶກ"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"ປາຍທາງ"</string>
     <string name="label_copies" msgid="3634531042822968308">"ສຳເນົາ"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"ຂະໜາດຂອງໜ້າເຈ້ຍ"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"ສີ"</string>
     <string name="label_orientation" msgid="2853142581990496477">"ລວງ"</string>
     <string name="label_pages" msgid="6300874667546617333">"(<xliff:g id="PAGE_COUNT">%1$s</xliff:g>) ໜ້າ"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"ບໍ່ມີການເຊື່ອມຕໍ່ຫາເຄື່ອງພິມ"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ບໍ່ຮູ້ຈັກ"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - ບໍ່ມີຢູ່"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"ບໍ່​ສາ​ມາດ​ສ້າງວຽກພິມໄດ້"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"ຂາວດຳ"</item>
     <item msgid="2762241247228983754">"ສີ"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"ທັງໝົດ"</item>
     <item msgid="6812869625222503603">"ໄລຍະ"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"ບໍ່​ສາ​ມາດ​ສ້າງວຽກພິມໄດ້"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-lt/strings.xml b/packages/PrintSpooler/res/values-lt/strings.xml
index 0c4e386..0fff75a 100644
--- a/packages/PrintSpooler/res/values-lt/strings.xml
+++ b/packages/PrintSpooler/res/values-lt/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Spausdintuvo nustatymai"</string>
-    <string name="print_button" msgid="645164566271246268">"Spausdinti"</string>
-    <string name="save_button" msgid="1921310454071758999">"Išsaugoti"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Paskirties vieta"</string>
     <string name="label_copies" msgid="3634531042822968308">"Kopijos"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Popieriaus dydis"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Spalva"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientacija"</string>
     <string name="label_pages" msgid="6300874667546617333">"Puslapiai (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nėra ryšio su spausdintuvu"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"nežinoma"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"„<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>“ – nepasiekiama"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Nepavyko sukurti spausdinimo užduoties"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Nespalvotas"</item>
     <item msgid="2762241247228983754">"Spalva"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Visi"</item>
     <item msgid="6812869625222503603">"Diapazonas"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Nepavyko sukurti spausdinimo užduoties"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-lv/strings.xml b/packages/PrintSpooler/res/values-lv/strings.xml
index 3fffdfe..55ae463 100644
--- a/packages/PrintSpooler/res/values-lv/strings.xml
+++ b/packages/PrintSpooler/res/values-lv/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Printera iestatījumi"</string>
-    <string name="print_button" msgid="645164566271246268">"Drukāt"</string>
-    <string name="save_button" msgid="1921310454071758999">"Saglabāt"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Galamērķis"</string>
     <string name="label_copies" msgid="3634531042822968308">"Eksemplāri"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Papīra izmērs"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Krāsa"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Virziens"</string>
     <string name="label_pages" msgid="6300874667546617333">"Lapas (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nav savienojuma ar printeri"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"nezināms"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> — nav pieejams"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Nevarēja ģenerēt drukas darbu"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Melnbalts"</item>
     <item msgid="2762241247228983754">"Krāsa"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Visi"</item>
     <item msgid="6812869625222503603">"Diapazons"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Nevarēja ģenerēt drukas darbu"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-mn-rMN/strings.xml b/packages/PrintSpooler/res/values-mn-rMN/strings.xml
index e429387..7eb9b06 100644
--- a/packages/PrintSpooler/res/values-mn-rMN/strings.xml
+++ b/packages/PrintSpooler/res/values-mn-rMN/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Хэвлэгчийн буфер"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Принтерийн тохиргоо"</string>
-    <string name="print_button" msgid="645164566271246268">"Хэвлэх"</string>
-    <string name="save_button" msgid="1921310454071758999">"Хадгалах"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Хүлээн авагч"</string>
     <string name="label_copies" msgid="3634531042822968308">"Хуулбарууд"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Цаасны хэмжээ"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Өнгө"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Чиглэл"</string>
     <string name="label_pages" msgid="6300874667546617333">"(<xliff:g id="PAGE_COUNT">%1$s</xliff:g>) хуудас"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Принтер холбогдоогүй байна"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"тодорхойгүй"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ашиглах боломжгүй"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Хэвлэх ажлыг үүсгэж чадсангүй"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Хар &amp; Цагаан"</item>
     <item msgid="2762241247228983754">"Өнгө"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Бүгд"</item>
     <item msgid="6812869625222503603">"Хүрээ"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Хэвлэх ажлыг үүсгэж чадсангүй"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-ms-rMY/strings.xml b/packages/PrintSpooler/res/values-ms-rMY/strings.xml
index cca5d2c..fa4abc9 100644
--- a/packages/PrintSpooler/res/values-ms-rMY/strings.xml
+++ b/packages/PrintSpooler/res/values-ms-rMY/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Penspul Cetakan"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Tetapan pencetak"</string>
-    <string name="print_button" msgid="645164566271246268">"Cetak"</string>
-    <string name="save_button" msgid="1921310454071758999">"Simpan"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Destinasi"</string>
     <string name="label_copies" msgid="3634531042822968308">"Salinan"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Saiz Kertas"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Warna"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientasi"</string>
     <string name="label_pages" msgid="6300874667546617333">"Halaman (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Tiada sambungan ke pencetak"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"tidak diketahui"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – tidak tersedia"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Tidak dapat menjana kerja cetakan"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Hitam &amp; Putih"</item>
     <item msgid="2762241247228983754">"Warna"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Semua"</item>
     <item msgid="6812869625222503603">"Julat"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tidak dapat menjana kerja cetakan"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-nb/strings.xml b/packages/PrintSpooler/res/values-nb/strings.xml
index f6a60c6..03d5248 100644
--- a/packages/PrintSpooler/res/values-nb/strings.xml
+++ b/packages/PrintSpooler/res/values-nb/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Utskriftskø"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Skriverinnstillinger"</string>
-    <string name="print_button" msgid="645164566271246268">"Skriv ut"</string>
-    <string name="save_button" msgid="1921310454071758999">"Lagre"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Destinasjon"</string>
     <string name="label_copies" msgid="3634531042822968308">"Kopier"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Papirstørrelse"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Farge"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Retning"</string>
     <string name="label_pages" msgid="6300874667546617333">"Sider (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Ingen forbindelse med skriveren"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ukjent"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – utilgjengelig"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Kunne ikke generere utskriftsjobben"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Svart og hvitt"</item>
     <item msgid="2762241247228983754">"Farge"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Alle"</item>
     <item msgid="6812869625222503603">"Område"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Kunne ikke generere utskriftsjobben"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-nl/strings.xml b/packages/PrintSpooler/res/values-nl/strings.xml
index dc12508..a2e5391 100644
--- a/packages/PrintSpooler/res/values-nl/strings.xml
+++ b/packages/PrintSpooler/res/values-nl/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Afdrukspooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Printerinstellingen"</string>
-    <string name="print_button" msgid="645164566271246268">"Afdrukken"</string>
-    <string name="save_button" msgid="1921310454071758999">"Opslaan"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Bestemming"</string>
     <string name="label_copies" msgid="3634531042822968308">"Aantal exemplaren"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Papierformaat"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Kleur"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Stand"</string>
     <string name="label_pages" msgid="6300874667546617333">"Pagina\'s (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Geen verbinding met printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"onbekend"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – niet beschikbaar"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Kan de afdruktaak niet genereren"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Zwart-wit"</item>
     <item msgid="2762241247228983754">"Kleur"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Alle"</item>
     <item msgid="6812869625222503603">"Bereik"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Kan de afdruktaak niet genereren"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-pl/strings.xml b/packages/PrintSpooler/res/values-pl/strings.xml
index 81acc76..1500060 100644
--- a/packages/PrintSpooler/res/values-pl/strings.xml
+++ b/packages/PrintSpooler/res/values-pl/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Bufor wydruku"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Ustawienia drukarki"</string>
-    <string name="print_button" msgid="645164566271246268">"Drukuj"</string>
-    <string name="save_button" msgid="1921310454071758999">"Zapisz"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Miejsce docelowe"</string>
     <string name="label_copies" msgid="3634531042822968308">"Kopie"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Rozmiar papieru"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Kolor"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientacja"</string>
     <string name="label_pages" msgid="6300874667546617333">"Strony (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Brak połączenia z drukarką"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"brak informacji"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – niedostępne"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Nie udało się wygenerować zadania drukowania"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Czarno-białe"</item>
     <item msgid="2762241247228983754">"Kolor"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Wszystkie"</item>
     <item msgid="6812869625222503603">"Zakres"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Nie udało się wygenerować zadania drukowania"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-pt-rPT/strings.xml b/packages/PrintSpooler/res/values-pt-rPT/strings.xml
index 6bfc395..9bcd0d6 100644
--- a/packages/PrintSpooler/res/values-pt-rPT/strings.xml
+++ b/packages/PrintSpooler/res/values-pt-rPT/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Definições da impressora"</string>
-    <string name="print_button" msgid="645164566271246268">"Imprimir"</string>
-    <string name="save_button" msgid="1921310454071758999">"Guardar"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Destino"</string>
     <string name="label_copies" msgid="3634531042822968308">"Cópias"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Tamanho do papel"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Cor"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientação"</string>
     <string name="label_pages" msgid="6300874667546617333">"Páginas (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Sem ligação à impressora"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"desconhecido"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – indisponível"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Não foi possível gerar a tarefa de impressão"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Preto e branco"</item>
     <item msgid="2762241247228983754">"Cor"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Todas"</item>
     <item msgid="6812869625222503603">"Intervalo"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Não foi possível gerar a tarefa de impressão"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-pt/strings.xml b/packages/PrintSpooler/res/values-pt/strings.xml
index 82c157d..f10e82e 100644
--- a/packages/PrintSpooler/res/values-pt/strings.xml
+++ b/packages/PrintSpooler/res/values-pt/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Sp. de impressão"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Configur. da impressora"</string>
-    <string name="print_button" msgid="645164566271246268">"Imprimir"</string>
-    <string name="save_button" msgid="1921310454071758999">"Salvar"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Destino"</string>
     <string name="label_copies" msgid="3634531042822968308">"Cópias"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Tamanho do papel"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Cor"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientação"</string>
     <string name="label_pages" msgid="6300874667546617333">"Páginas (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Sem conexão com a impressora"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"desconhecido"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – não disponível"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Não foi possível gerar o trabalho de impressão"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Preto e branco"</item>
     <item msgid="2762241247228983754">"Cor"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Todas"</item>
     <item msgid="6812869625222503603">"Intervalo"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Não foi possível gerar o trabalho de impressão"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-ro/strings.xml b/packages/PrintSpooler/res/values-ro/strings.xml
index 79c3bdc..18394b0 100644
--- a/packages/PrintSpooler/res/values-ro/strings.xml
+++ b/packages/PrintSpooler/res/values-ro/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Derulator print."</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Setările imprimantei"</string>
-    <string name="print_button" msgid="645164566271246268">"Printați"</string>
-    <string name="save_button" msgid="1921310454071758999">"Salvați"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Destinație"</string>
     <string name="label_copies" msgid="3634531042822968308">"Copii"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Formatul hârtiei"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Color"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientare"</string>
     <string name="label_pages" msgid="6300874667546617333">"Pagini (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nu există conexiune la o imprimantă"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"necunoscut"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - indisponibil"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Nu s-a putut genera sarcina de printare"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Alb-negru"</item>
     <item msgid="2762241247228983754">"Color"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Toate"</item>
     <item msgid="6812869625222503603">"Interval"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Nu s-a putut genera sarcina de printare"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-ru/strings.xml b/packages/PrintSpooler/res/values-ru/strings.xml
index a27f3c8..c474752 100644
--- a/packages/PrintSpooler/res/values-ru/strings.xml
+++ b/packages/PrintSpooler/res/values-ru/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Спулер печати"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Настройки принтера"</string>
-    <string name="print_button" msgid="645164566271246268">"Печать"</string>
-    <string name="save_button" msgid="1921310454071758999">"Сохранить"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Принтер"</string>
     <string name="label_copies" msgid="3634531042822968308">"Копии"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Формат"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Печать"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Ориентация"</string>
     <string name="label_pages" msgid="6300874667546617333">"СТРАНИЦЫ (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Нет связи с принтером"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"неизвестно"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – недоступен"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Не удалось отправить документ на печать."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Черно-белая"</item>
     <item msgid="2762241247228983754">"Цветная"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Все"</item>
     <item msgid="6812869625222503603">"Диапазон"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Не удалось отправить документ на печать."</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-sk/strings.xml b/packages/PrintSpooler/res/values-sk/strings.xml
index 39a022d..59e7f4c 100644
--- a/packages/PrintSpooler/res/values-sk/strings.xml
+++ b/packages/PrintSpooler/res/values-sk/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Zaraďovač tlače"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Nastavenia tlačiarne"</string>
-    <string name="print_button" msgid="645164566271246268">"Tlačiť"</string>
-    <string name="save_button" msgid="1921310454071758999">"Uložiť"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Cieľ"</string>
     <string name="label_copies" msgid="3634531042822968308">"Kópie"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Veľkosť papiera"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Farba"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientácia"</string>
     <string name="label_pages" msgid="6300874667546617333">"STRÁNKY (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Žiadne pripojenie k tlačiarni"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"neznáme"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – nie je k dispozícii"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Tlačovú úlohu nie je možné vytvoriť"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Čiernobiele"</item>
     <item msgid="2762241247228983754">"Farba"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Všetky"</item>
     <item msgid="6812869625222503603">"Rozsah"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tlačovú úlohu nie je možné vytvoriť"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-sl/strings.xml b/packages/PrintSpooler/res/values-sl/strings.xml
index e299508..3d27d65 100644
--- a/packages/PrintSpooler/res/values-sl/strings.xml
+++ b/packages/PrintSpooler/res/values-sl/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Tisk. v ozadju"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Nastavitve tiskalnika"</string>
-    <string name="print_button" msgid="645164566271246268">"Natisni"</string>
-    <string name="save_button" msgid="1921310454071758999">"Shrani"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Cilj"</string>
     <string name="label_copies" msgid="3634531042822968308">"Št. kopij"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Velikost papirja"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Barvno"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Postavitev"</string>
     <string name="label_pages" msgid="6300874667546617333">"Št. strani (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Ni povezave s tiskalnikom"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"neznano"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ni na voljo"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Tiskalnega opravila ni bilo mogoče ustvariti"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Črno-belo"</item>
     <item msgid="2762241247228983754">"Barvno"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Vse"</item>
     <item msgid="6812869625222503603">"Obseg"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tiskalnega opravila ni bilo mogoče ustvariti"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-sr/strings.xml b/packages/PrintSpooler/res/values-sr/strings.xml
index cbab1a6..b3c13a0 100644
--- a/packages/PrintSpooler/res/values-sr/strings.xml
+++ b/packages/PrintSpooler/res/values-sr/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Штамп. из мемор."</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Подешавања штампача"</string>
-    <string name="print_button" msgid="645164566271246268">"Штампај"</string>
-    <string name="save_button" msgid="1921310454071758999">"Сачувај"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Одредиште"</string>
     <string name="label_copies" msgid="3634531042822968308">"Копије"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Величина папира"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Боја"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Положај"</string>
     <string name="label_pages" msgid="6300874667546617333">"Странице (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Нема везе са штампачем"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"непознато"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – недоступан"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Није могуће генерисати задатак за штампање"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Црно-бело"</item>
     <item msgid="2762241247228983754">"Боја"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Све"</item>
     <item msgid="6812869625222503603">"Опсег"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Није могуће генерисати задатак за штампање"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-sv/strings.xml b/packages/PrintSpooler/res/values-sv/strings.xml
index 2286fce..58dfa40 100644
--- a/packages/PrintSpooler/res/values-sv/strings.xml
+++ b/packages/PrintSpooler/res/values-sv/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Utskriftskö"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Skrivarinställningar"</string>
-    <string name="print_button" msgid="645164566271246268">"Skriv ut"</string>
-    <string name="save_button" msgid="1921310454071758999">"Spara"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Destination"</string>
     <string name="label_copies" msgid="3634531042822968308">"Kopior"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Pappersstorlek"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Färg"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientering"</string>
     <string name="label_pages" msgid="6300874667546617333">"Sidor (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Ingen anslutning till skrivaren"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"okänt"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – inte tillgänglig"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Det gick inte att skapa utskriftsjobbet"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Svartvit"</item>
     <item msgid="2762241247228983754">"Färg"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Alla"</item>
     <item msgid="6812869625222503603">"Intervall"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Det gick inte att skapa utskriftsjobbet"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-sw/strings.xml b/packages/PrintSpooler/res/values-sw/strings.xml
index a84e9b3..0bb9790 100644
--- a/packages/PrintSpooler/res/values-sw/strings.xml
+++ b/packages/PrintSpooler/res/values-sw/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Programu ya kuandaa Printa kwa ajili ya Kuchapisha"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Mipangilio ya printa"</string>
-    <string name="print_button" msgid="645164566271246268">"Chapisha"</string>
-    <string name="save_button" msgid="1921310454071758999">"Hifadhi"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Itakapofika"</string>
     <string name="label_copies" msgid="3634531042822968308">"Nakala"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Ukubwa wa Karatasi"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Rangi"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Mkao"</string>
     <string name="label_pages" msgid="6300874667546617333">"Kurasa (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Hakuna muunganisho kwa printa"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"haijulikani"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - haipatikani"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Haikuweza kuleta kazi ya kuchapisha"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Nyeusi na Nyeupe"</item>
     <item msgid="2762241247228983754">"Rangi"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Zote"</item>
     <item msgid="6812869625222503603">"Masafa"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Haikuweza kuleta kazi ya kuchapisha"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-sw600dp-land/constants.xml b/packages/PrintSpooler/res/values-sw600dp-land/constants.xml
new file mode 100644
index 0000000..cacdf98
--- /dev/null
+++ b/packages/PrintSpooler/res/values-sw600dp-land/constants.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+
+    <integer name="print_option_column_count">6</integer>
+
+</resources>
diff --git a/packages/PrintSpooler/res/values-sw600dp/constants.xml b/packages/PrintSpooler/res/values-sw600dp/constants.xml
new file mode 100644
index 0000000..14c099c
--- /dev/null
+++ b/packages/PrintSpooler/res/values-sw600dp/constants.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<resources>
+
+    <integer name="print_option_column_count">3</integer>
+
+</resources>
diff --git a/packages/PrintSpooler/res/values-th/strings.xml b/packages/PrintSpooler/res/values-th/strings.xml
index aa01d6f..91fc544 100644
--- a/packages/PrintSpooler/res/values-th/strings.xml
+++ b/packages/PrintSpooler/res/values-th/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"การตั้งค่าเครื่องพิมพ์"</string>
-    <string name="print_button" msgid="645164566271246268">"พิมพ์"</string>
-    <string name="save_button" msgid="1921310454071758999">"บันทึก"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"ปลายทาง"</string>
     <string name="label_copies" msgid="3634531042822968308">"สำเนา"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"ขนาดของกระดาษ"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"สี"</string>
     <string name="label_orientation" msgid="2853142581990496477">"การวางแนว"</string>
     <string name="label_pages" msgid="6300874667546617333">"หน้า (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"ไม่มีการเชื่อมต่อไปยังเครื่องพิมพ์"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ไม่ทราบ"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ไม่พร้อมใช้งาน"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"ไม่สามารถสร้างงานพิมพ์"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"ขาวดำ"</item>
     <item msgid="2762241247228983754">"สี"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"ทั้งหมด"</item>
     <item msgid="6812869625222503603">"ช่วง"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"ไม่สามารถสร้างงานพิมพ์"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-tl/strings.xml b/packages/PrintSpooler/res/values-tl/strings.xml
index a72c937..111fee4 100644
--- a/packages/PrintSpooler/res/values-tl/strings.xml
+++ b/packages/PrintSpooler/res/values-tl/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Mga setting ng printer"</string>
-    <string name="print_button" msgid="645164566271246268">"I-print"</string>
-    <string name="save_button" msgid="1921310454071758999">"I-save"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Patutunguhan"</string>
     <string name="label_copies" msgid="3634531042822968308">"Mga Kopya"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Laki ng Papel"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Kulay"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Oryentasyon"</string>
     <string name="label_pages" msgid="6300874667546617333">"Mga Pahina (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Hindi nakakonekta sa printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"hindi alam"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – hindi available"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Hindi mabuo ang pag-print"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Black &amp; White"</item>
     <item msgid="2762241247228983754">"Kulay"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Lahat"</item>
     <item msgid="6812869625222503603">"Sakop"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Hindi mabuo ang pag-print"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-tr/strings.xml b/packages/PrintSpooler/res/values-tr/strings.xml
index c6e302d..63fa270 100644
--- a/packages/PrintSpooler/res/values-tr/strings.xml
+++ b/packages/PrintSpooler/res/values-tr/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Yazıcı ayarları"</string>
-    <string name="print_button" msgid="645164566271246268">"Yazdır"</string>
-    <string name="save_button" msgid="1921310454071758999">"Kaydet"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Hedef"</string>
     <string name="label_copies" msgid="3634531042822968308">"Kopya sayısı"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Kağıt Boyutu"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Renkli"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Sayfa yönü"</string>
     <string name="label_pages" msgid="6300874667546617333">"Sayfa (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Yazıcı bağlantısı yok"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"bilinmiyor"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – kullanılamıyor"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Yazdırma işi oluşturulamadı"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Siyah Beyaz"</item>
     <item msgid="2762241247228983754">"Renkli"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Tümü"</item>
     <item msgid="6812869625222503603">"Aralık"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Yazdırma işi oluşturulamadı"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-uk/strings.xml b/packages/PrintSpooler/res/values-uk/strings.xml
index 4f526d0..bb0ee2c 100644
--- a/packages/PrintSpooler/res/values-uk/strings.xml
+++ b/packages/PrintSpooler/res/values-uk/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Налаштування принтера"</string>
-    <string name="print_button" msgid="645164566271246268">"Друк"</string>
-    <string name="save_button" msgid="1921310454071758999">"Зберегти"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Місце признач-ня"</string>
     <string name="label_copies" msgid="3634531042822968308">"Копії"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Розмір паперу"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Колір"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Орієнтація"</string>
     <string name="label_pages" msgid="6300874667546617333">"Сторінки (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Немає з’єднання з принтером"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"невідомо"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"Завдання \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" не доступне"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Не вдалося створити завдання друку"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Чорно-білий"</item>
     <item msgid="2762241247228983754">"Колір"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Усі"</item>
     <item msgid="6812869625222503603">"Діапазон"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Не вдалося створити завдання друку"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-vi/strings.xml b/packages/PrintSpooler/res/values-vi/strings.xml
index d21172c..0c42ccc 100644
--- a/packages/PrintSpooler/res/values-vi/strings.xml
+++ b/packages/PrintSpooler/res/values-vi/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Cài đặt máy in"</string>
-    <string name="print_button" msgid="645164566271246268">"In"</string>
-    <string name="save_button" msgid="1921310454071758999">"Lưu"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Đích"</string>
     <string name="label_copies" msgid="3634531042822968308">"Bản sao"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Kích thước trang"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Màu"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Hướng"</string>
     <string name="label_pages" msgid="6300874667546617333">"Trang (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Không có kết nối nào với máy in"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"không xác định"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – không khả dụng"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Không thể tạo lệnh in"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Đen trắng"</item>
     <item msgid="2762241247228983754">"Màu"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Tất cả"</item>
     <item msgid="6812869625222503603">"Dãy"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Không thể tạo lệnh in"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-zh-rCN/strings.xml b/packages/PrintSpooler/res/values-zh-rCN/strings.xml
index e43ea60..c853d54 100644
--- a/packages/PrintSpooler/res/values-zh-rCN/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rCN/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"打印处理服务"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"打印机设置"</string>
-    <string name="print_button" msgid="645164566271246268">"打印"</string>
-    <string name="save_button" msgid="1921310454071758999">"保存"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"目的地"</string>
     <string name="label_copies" msgid="3634531042822968308">"份数"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"纸张尺寸"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"颜色"</string>
     <string name="label_orientation" msgid="2853142581990496477">"方向"</string>
     <string name="label_pages" msgid="6300874667546617333">"页数 (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"未与打印机建立连接"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"未知"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - 无法使用"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"无法生成打印作业"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"黑白"</item>
     <item msgid="2762241247228983754">"彩色"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"全部"</item>
     <item msgid="6812869625222503603">"范围"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"无法生成打印作业"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-zh-rHK/strings.xml b/packages/PrintSpooler/res/values-zh-rHK/strings.xml
index 2c2422a9..5d75382 100644
--- a/packages/PrintSpooler/res/values-zh-rHK/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rHK/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"列印多工緩衝處理器"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"打印機設定"</string>
-    <string name="print_button" msgid="645164566271246268">"列印"</string>
-    <string name="save_button" msgid="1921310454071758999">"儲存"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"目的地"</string>
     <string name="label_copies" msgid="3634531042822968308">"份數"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"紙張大小"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"顏色"</string>
     <string name="label_orientation" msgid="2853142581990496477">"方向"</string>
     <string name="label_pages" msgid="6300874667546617333">"頁數 (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"尚未與打印機連線"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"不明"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – 無法使用"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"無法產生列印工作"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"黑白"</item>
     <item msgid="2762241247228983754">"彩色"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"全部"</item>
     <item msgid="6812869625222503603">"範圍"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"無法產生列印工作"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-zh-rTW/strings.xml b/packages/PrintSpooler/res/values-zh-rTW/strings.xml
index 0fe88d9..a2e1637 100644
--- a/packages/PrintSpooler/res/values-zh-rTW/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rTW/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"列印多工緩衝處理器"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"印表機設定"</string>
-    <string name="print_button" msgid="645164566271246268">"列印"</string>
-    <string name="save_button" msgid="1921310454071758999">"儲存"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"目的地"</string>
     <string name="label_copies" msgid="3634531042822968308">"份數"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"紙張大小"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"色彩"</string>
     <string name="label_orientation" msgid="2853142581990496477">"方向"</string>
     <string name="label_pages" msgid="6300874667546617333">"頁數 (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"尚未與印表機建立連線"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"不明"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – 無法使用"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"無法產生列印工作"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"黑白"</item>
     <item msgid="2762241247228983754">"彩色"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"全部"</item>
     <item msgid="6812869625222503603">"範圍"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"無法產生列印工作"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values-zu/strings.xml b/packages/PrintSpooler/res/values-zu/strings.xml
index 08e31e1..2837949 100644
--- a/packages/PrintSpooler/res/values-zu/strings.xml
+++ b/packages/PrintSpooler/res/values-zu/strings.xml
@@ -17,12 +17,16 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Ispuli sephrinta"</string>
-    <string name="advanced_settings_button" msgid="5764225091289415632">"Izilungiselelo zephrinta"</string>
-    <string name="print_button" msgid="645164566271246268">"Phrinta"</string>
-    <string name="save_button" msgid="1921310454071758999">"Londoloza"</string>
+    <!-- no translation found for more_options_button (2243228396432556771) -->
+    <skip />
     <string name="label_destination" msgid="9132510997381599275">"Indawo"</string>
     <string name="label_copies" msgid="3634531042822968308">"Amakhophi"</string>
-    <string name="label_paper_size" msgid="8681895607876809323">"Usayizi wephepha"</string>
+    <!-- no translation found for label_copies_summary (3861966063536529540) -->
+    <skip />
+    <!-- no translation found for label_paper_size (908654383827777759) -->
+    <skip />
+    <!-- no translation found for label_paper_size_summary (5668204981332138168) -->
+    <skip />
     <string name="label_color" msgid="1108690305218188969">"Umbala"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Umumo"</string>
     <string name="label_pages" msgid="6300874667546617333">"Amakhasi (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
@@ -63,7 +67,6 @@
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Akukho ukuxhumana kuphrinta"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"akwaziwa"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"I-<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ayitholakali"</string>
-    <string name="print_error_default_message" msgid="8568506918983980567">"Ayikwazanga ukukhiqiza umsebenzi wokuphrinta"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Okumnyama nokumhlophe"</item>
     <item msgid="2762241247228983754">"Umbala"</item>
@@ -76,4 +79,13 @@
     <item msgid="7421377442011699994">"Konke"</item>
     <item msgid="6812869625222503603">"Ibanga"</item>
   </string-array>
+    <!-- no translation found for print_write_error_message (5787642615179572543) -->
+    <skip />
+    <string name="print_error_default_message" msgid="8568506918983980567">"Ayikwazanga ukukhiqiza umsebenzi wokuphrinta"</string>
+    <!-- no translation found for print_error_retry (1426421728784259538) -->
+    <skip />
+    <!-- no translation found for print_error_printer_unavailable (6653128543854282851) -->
+    <skip />
+    <!-- no translation found for print_operation_canceling (5274571823242489160) -->
+    <skip />
 </resources>
diff --git a/packages/PrintSpooler/res/values/attrs.xml b/packages/PrintSpooler/res/values/attrs.xml
new file mode 100644
index 0000000..feb8be1
--- /dev/null
+++ b/packages/PrintSpooler/res/values/attrs.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+
+    <declare-styleable name="PrintOptionsLayout">
+
+        <attr name="columnCount" format="integer" />
+
+    </declare-styleable>
+
+</resources>
diff --git a/packages/PrintSpooler/res/values/colors.xml b/packages/PrintSpooler/res/values/colors.xml
index 4fc25b3..fd6ae195 100644
--- a/packages/PrintSpooler/res/values/colors.xml
+++ b/packages/PrintSpooler/res/values/colors.xml
@@ -16,10 +16,6 @@
 
 <resources>
 
-    <color name="container_background">#F2F2F2</color>
-    <color name="important_text">#333333</color>
-    <color name="print_option_title">#888888</color>
-    <color name="separator">#CCCCCC</color>
-    <color name="action_button_background">#FFFFFF</color>
+    <color name="print_button_tint_color">#EEFF41</color>
 
 </resources>
diff --git a/packages/PrintSpooler/res/values/constants.xml b/packages/PrintSpooler/res/values/constants.xml
index e9c925c..9a2c14e 100644
--- a/packages/PrintSpooler/res/values/constants.xml
+++ b/packages/PrintSpooler/res/values/constants.xml
@@ -19,6 +19,8 @@
     <integer name="page_option_value_all">0</integer>
     <integer name="page_option_value_page_range">1</integer>
 
+    <integer name="print_option_column_count">2</integer>
+
     <integer-array name="page_options_values" translatable="false">
         <item>@integer/page_option_value_all</item>
         <item>@integer/page_option_value_page_range</item>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index d2613d0..d85529c 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -19,14 +19,8 @@
     <!-- Title of the PrintSpooler application. [CHAR LIMIT=50] -->
     <string name="app_label">Print Spooler</string>
 
-    <!-- Label of the print dialog's button for advanced printer settings. [CHAR LIMIT=25] -->
-    <string name="advanced_settings_button">Printer settings</string>
-
-    <!-- Label of the print dialog's print button. [CHAR LIMIT=16] -->
-    <string name="print_button">Print</string>
-
-    <!-- Label of the print dialog's save button. [CHAR LIMIT=16] -->
-    <string name="save_button">Save</string>
+    <!-- Label of the print dialog's button for more print options. [CHAR LIMIT=25] -->
+    <string name="more_options_button">More options</string>
 
     <!-- Label of the destination widget. [CHAR LIMIT=20] -->
     <string name="label_destination">Destination</string>
@@ -34,8 +28,14 @@
     <!-- Label of the copies count widget. [CHAR LIMIT=20] -->
     <string name="label_copies">Copies</string>
 
+    <!-- Label of the copies count for the print options summary. [CHAR LIMIT=20] -->
+    <string name="label_copies_summary">Copies:</string>
+
     <!-- Label of the paper size widget. [CHAR LIMIT=20] -->
-    <string name="label_paper_size">Paper Size</string>
+    <string name="label_paper_size">Paper size</string>
+
+    <!-- Label of the paper size for the print options summary. [CHAR LIMIT=20] -->
+    <string name="label_paper_size_summary">Paper size:</string>
 
     <!-- Label of the color mode widget. [CHAR LIMIT=20] -->
     <string name="label_color">Color</string>
@@ -118,19 +118,19 @@
 
     <!-- Notifications -->
 
-    <!-- Template for the notificaiton label for a printing print job. [CHAR LIMIT=25] -->
+    <!-- Template for the notification label for a printing print job. [CHAR LIMIT=25] -->
     <string name="printing_notification_title_template">Printing <xliff:g id="print_job_name" example="foo.jpg">%1$s</xliff:g></string>
 
-    <!-- Template for the notificaiton label for a cancelling print job. [CHAR LIMIT=25] -->
+    <!-- Template for the notification label for a cancelling print job. [CHAR LIMIT=25] -->
     <string name="cancelling_notification_title_template">Cancelling <xliff:g id="print_job_name" example="foo.jpg">%1$s</xliff:g></string>
 
-    <!-- Template for the notificaiton label for a failed print job. [CHAR LIMIT=25] -->
+    <!-- Template for the notification label for a failed print job. [CHAR LIMIT=25] -->
     <string name="failed_notification_title_template">Printer error <xliff:g id="print_job_name" example="foo.jpg">%1$s</xliff:g></string>
 
-    <!-- Template for the notificaiton label for a blocked print job. [CHAR LIMIT=25] -->
+    <!-- Template for the notification label for a blocked print job. [CHAR LIMIT=25] -->
     <string name="blocked_notification_title_template">Printer blocked <xliff:g id="print_job_name" example="foo.jpg">%1$s</xliff:g></string>
 
-    <!-- Template for the notificaiton label for a composite (multiple items) print jobs notification. [CHAR LIMIT=25] -->
+    <!-- Template for the notification label for a composite (multiple items) print jobs notification. [CHAR LIMIT=25] -->
     <plurals name="composite_notification_title_template">
         <item quantity="one"><xliff:g id="print_job_name" example="foo.jpg">%1$d</xliff:g> print job</item>
         <item quantity="other"><xliff:g id="print_job_name" example="foo.jpg">%1$d</xliff:g> print jobs</item>
@@ -139,7 +139,7 @@
     <!-- Label for the notification button for cancelling a print job. [CHAR LIMIT=25] -->
     <string name="cancel">Cancel</string>
 
-    <!-- Label for the notification button for restrating a filed print job. [CHAR LIMIT=25] -->
+    <!-- Label for the notification button for restarting a filed print job. [CHAR LIMIT=25] -->
     <string name="restart">Restart</string>
 
     <!-- Message that there is no connection to a printer. [CHAR LIMIT=40] -->
@@ -151,9 +151,6 @@
     <!-- Label for a printer that is not available. [CHAR LIMIT=25] -->
     <string name="printer_unavailable"><xliff:g id="print_job_name" example="Canon-123GHT">%1$s</xliff:g> &#8211; unavailable</string>
 
-    <!-- Default message of an alert dialog for app error while generating a print job. [CHAR LIMIT=50] -->
-    <string name="print_error_default_message">Couldn\'t generate print job</string>
-
     <!-- Arrays -->
 
     <!-- Color mode labels. -->
@@ -200,4 +197,23 @@
         holder to start the configuration activities of a print service. Should never be needed
         for normal apps.</string>
 
+    <!-- Error messages -->
+
+    <!-- Message for an error when trying to print to a PDF file. [CHAR LIMIT=50] -->
+    <string name="print_write_error_message">Couldn\'t write to file</string>
+
+    <!-- Default message for an error while generating a print job. [CHAR LIMIT=50] -->
+    <string name="print_error_default_message">Couldn\'t generate print job</string>
+
+    <!-- Label for the retry button in the error message. [CHAR LIMIT=50] -->
+    <string name="print_error_retry">Retry</string>
+
+    <!-- Message for the currently selected printer becoming unavailable. [CHAR LIMIT=50] -->
+    <string name="print_error_printer_unavailable">Printer unavailable</string>
+
+    <!-- Long running operations -->
+
+    <!-- Message for the cancel print operation UI while waiting for cancel to be performed. [CHAR LIMIT=50] -->
+    <string name="print_operation_canceling">Cancelling\u2026</string>
+
 </resources>
diff --git a/packages/PrintSpooler/res/values/styles.xml b/packages/PrintSpooler/res/values/styles.xml
index d64380a..9637847 100644
--- a/packages/PrintSpooler/res/values/styles.xml
+++ b/packages/PrintSpooler/res/values/styles.xml
@@ -16,13 +16,6 @@
 
 <resources>
 
-    <style name="PrintOptionTitleTextAppearance">
-        <item name="android:textStyle">normal</item>
-        <item name="android:textSize">14sp</item>
-        <item name="android:textAllCaps">true</item>
-        <item name="android:textColor">@color/print_option_title</item>
-    </style>
-
     <style name="PrintOptionSpinnerStyle">
         <item name="android:paddingTop">0dip</item>
         <item name="android:paddingBottom">0dip</item>
@@ -30,7 +23,7 @@
     </style>
 
     <style name="PrintOptionEditTextStyle">
-         <item name="android:minHeight">?android:attr/listPreferredItemHeightSmall</item>
+
          <item name="android:singleLine">true</item>
          <item name="android:ellipsize">end</item>
     </style>
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index 94ab895..40bf725 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -16,15 +16,6 @@
 
 <resources>
 
-    <style name="PrintJobConfigActivityTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
-        <item name="android:windowBackground">@android:color/transparent</item>
-        <item name="android:windowSoftInputMode">stateAlwaysHidden|adjustResize</item>
-        <item name="android:windowIsTranslucent">true</item>
-        <item name="android:backgroundDimEnabled">true</item>
-        <item name="android:colorBackgroundCacheHint">@android:color/transparent</item>
-        <item name="android:windowIsFloating">true</item>
-    </style>
-
     <style name="SelectPrinterActivityTheme" parent="@android:style/Theme.DeviceDefault.Light">
         <item name="android:actionBarStyle">@style/SelectPrinterActivityActionBarStyle</item>
     </style>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintDialogFrame.java b/packages/PrintSpooler/src/com/android/printspooler/PrintDialogFrame.java
deleted file mode 100644
index c1c4d21..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintDialogFrame.java
+++ /dev/null
@@ -1,69 +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.printspooler;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-
-public class PrintDialogFrame extends FrameLayout {
-
-    public final int mMaxWidth;
-
-    public int mHeight;
-
-    public PrintDialogFrame(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mMaxWidth = context.getResources().getDimensionPixelSize(
-                R.dimen.print_dialog_frame_max_width_dip);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-        int measuredWidth  = getMeasuredWidth();
-        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
-        switch (widthMode) {
-            case MeasureSpec.UNSPECIFIED: {
-                measuredWidth = mMaxWidth;
-            } break;
-
-            case MeasureSpec.AT_MOST: {
-                final int receivedWidth = MeasureSpec.getSize(widthMeasureSpec);
-                measuredWidth = Math.min(mMaxWidth, receivedWidth);
-            } break;
-        }
-
-        mHeight = Math.max(mHeight, getMeasuredHeight());
-
-        int measuredHeight  = getMeasuredHeight();
-        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
-        switch (heightMode) {
-            case MeasureSpec.UNSPECIFIED: {
-                measuredHeight = mHeight;
-            } break;
-
-             case MeasureSpec.AT_MOST: {
-                final int receivedHeight = MeasureSpec.getSize(heightMeasureSpec);
-                measuredHeight = Math.min(mHeight, receivedHeight);
-            } break;
-        }
-
-        setMeasuredDimension(measuredWidth, measuredHeight);
-    }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
deleted file mode 100644
index e3d8d05..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
+++ /dev/null
@@ -1,2966 +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.printspooler;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.app.LoaderManager;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.Loader;
-import android.content.ServiceConnection;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.database.DataSetObserver;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.print.ILayoutResultCallback;
-import android.print.IPrintDocumentAdapter;
-import android.print.IPrintDocumentAdapterObserver;
-import android.print.IWriteResultCallback;
-import android.print.PageRange;
-import android.print.PrintAttributes;
-import android.print.PrintAttributes.Margins;
-import android.print.PrintAttributes.MediaSize;
-import android.print.PrintAttributes.Resolution;
-import android.print.PrintDocumentAdapter;
-import android.print.PrintDocumentInfo;
-import android.print.PrintJobId;
-import android.print.PrintJobInfo;
-import android.print.PrintManager;
-import android.print.PrinterCapabilitiesInfo;
-import android.print.PrinterId;
-import android.print.PrinterInfo;
-import android.printservice.PrintService;
-import android.printservice.PrintServiceInfo;
-import android.provider.DocumentsContract;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextUtils.SimpleStringSplitter;
-import android.text.TextWatcher;
-import android.util.ArrayMap;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.View.OnAttachStateChangeListener;
-import android.view.View.OnClickListener;
-import android.view.View.OnFocusChangeListener;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.view.ViewPropertyAnimator;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.ArrayAdapter;
-import android.widget.BaseAdapter;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.Spinner;
-import android.widget.TextView;
-
-import com.android.printspooler.MediaSizeUtils.MediaSizeComparator;
-
-import libcore.io.IoUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Activity for configuring a print job.
- */
-public class PrintJobConfigActivity extends Activity {
-
-    private static final String LOG_TAG = "PrintJobConfigActivity";
-
-    private static final boolean DEBUG = false;
-
-    public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
-
-    private static final int LOADER_ID_PRINTERS_LOADER = 1;
-
-    private static final int ORIENTATION_PORTRAIT = 0;
-    private static final int ORIENTATION_LANDSCAPE = 1;
-
-    private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9;
-
-    private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE;
-    private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1;
-
-    private static final int ACTIVITY_REQUEST_CREATE_FILE = 1;
-    private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2;
-    private static final int ACTIVITY_POPULATE_ADVANCED_PRINT_OPTIONS = 3;
-
-    private static final int CONTROLLER_STATE_FINISHED = 1;
-    private static final int CONTROLLER_STATE_FAILED = 2;
-    private static final int CONTROLLER_STATE_CANCELLED = 3;
-    private static final int CONTROLLER_STATE_INITIALIZED = 4;
-    private static final int CONTROLLER_STATE_STARTED = 5;
-    private static final int CONTROLLER_STATE_LAYOUT_STARTED = 6;
-    private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 7;
-    private static final int CONTROLLER_STATE_WRITE_STARTED = 8;
-    private static final int CONTROLLER_STATE_WRITE_COMPLETED = 9;
-
-    private static final int EDITOR_STATE_INITIALIZED = 1;
-    private static final int EDITOR_STATE_CONFIRMED_PRINT = 2;
-    private static final int EDITOR_STATE_CANCELLED = 3;
-
-    private static final int MIN_COPIES = 1;
-    private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES);
-
-    private static final Pattern PATTERN_DIGITS = Pattern.compile("[\\d]+");
-
-    private static final Pattern PATTERN_ESCAPE_SPECIAL_CHARS = Pattern.compile(
-            "(?=[]\\[+&|!(){}^\"~*?:\\\\])");
-
-    private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile(
-            "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*?(([,])"
-            + "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+");
-
-    public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {PageRange.ALL_PAGES};
-
-    private final PrintAttributes mOldPrintAttributes = new PrintAttributes.Builder().build();
-    private final PrintAttributes mCurrPrintAttributes = new PrintAttributes.Builder().build();
-
-    private final DeathRecipient mDeathRecipient = new DeathRecipient() {
-        @Override
-        public void binderDied() {
-            finish();
-        }
-    };
-
-    private Editor mEditor;
-    private Document mDocument;
-    private PrintController mController;
-
-    private PrintJobId mPrintJobId;
-
-    private IBinder mIPrintDocumentAdapter;
-
-    private Dialog mGeneratingPrintJobDialog;
-
-    private PrintSpoolerProvider mSpoolerProvider;
-
-    private String mCallingPackageName;
-
-    @Override
-    protected void onCreate(Bundle bundle) {
-        super.onCreate(bundle);
-
-        setTitle(R.string.print_dialog);
-
-        Bundle extras = getIntent().getExtras();
-
-        PrintJobInfo printJob = extras.getParcelable(PrintManager.EXTRA_PRINT_JOB);
-        if (printJob == null) {
-            throw new IllegalArgumentException("printJob cannot be null");
-        }
-
-        mPrintJobId = printJob.getId();
-        mIPrintDocumentAdapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER);
-        if (mIPrintDocumentAdapter == null) {
-            throw new IllegalArgumentException("PrintDocumentAdapter cannot be null");
-        }
-
-        try {
-            IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter)
-                    .setObserver(new PrintDocumentAdapterObserver(this));
-        } catch (RemoteException re) {
-            finish();
-            return;
-        }
-
-        PrintAttributes attributes = printJob.getAttributes();
-        if (attributes != null) {
-            mCurrPrintAttributes.copyFrom(attributes);
-        }
-
-        mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME);
-
-        setContentView(R.layout.print_job_config_activity_container);
-
-        try {
-            mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
-        } catch (RemoteException re) {
-            finish();
-            return;
-        }
-
-        mDocument = new Document();
-        mEditor = new Editor();
-
-        mSpoolerProvider = new PrintSpoolerProvider(this,
-                new Runnable() {
-            @Override
-            public void run() {
-                // We got the spooler so unleash the UI.
-                mController = new PrintController(new RemotePrintDocumentAdapter(
-                        IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
-                        mSpoolerProvider.getSpooler().generateFileForPrintJob(mPrintJobId)));
-                mController.initialize();
-
-                mEditor.initialize();
-                mEditor.postCreate();
-            }
-        });
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        if (mSpoolerProvider.getSpooler() != null) {
-            mEditor.refreshCurrentPrinter();
-        }
-    }
-
-    @Override
-    public void onPause() {
-       if (isFinishing()) {
-           if (mController != null && mController.hasStarted()) {
-               mController.finish();
-           }
-           if (mEditor != null && mEditor.isPrintConfirmed()
-                   && mController != null && mController.isFinished()) {
-                   mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId,
-                           PrintJobInfo.STATE_QUEUED, null);
-           } else {
-               mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId,
-                       PrintJobInfo.STATE_CANCELED, null);
-           }
-           if (mGeneratingPrintJobDialog != null) {
-               mGeneratingPrintJobDialog.dismiss();
-               mGeneratingPrintJobDialog = null;
-           }
-           mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
-           mSpoolerProvider.destroy();
-       }
-        super.onPause();
-    }
-
-    public boolean onTouchEvent(MotionEvent event) {
-        if (mController != null && mEditor != null &&
-                !mEditor.isPrintConfirmed() && mEditor.shouldCloseOnTouch(event)) {
-            if (!mController.isWorking()) {
-                PrintJobConfigActivity.this.finish();
-            }
-            mEditor.cancel();
-            return true;
-        }
-        return super.onTouchEvent(event);
-    }
-
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_BACK) {
-            event.startTracking();
-        }
-        return super.onKeyDown(keyCode, event);
-    }
-
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (mController != null && mEditor != null) {
-            if (keyCode == KeyEvent.KEYCODE_BACK) {
-                if (mEditor.isShwoingGeneratingPrintJobUi()) {
-                    return true;
-                }
-                if (event.isTracking() && !event.isCanceled()) {
-                    if (!mController.isWorking()) {
-                        PrintJobConfigActivity.this.finish();
-                    }
-                }
-                mEditor.cancel();
-                return true;
-            }
-        }
-        return super.onKeyUp(keyCode, event);
-    }
-
-    private boolean printAttributesChanged() {
-        return !mOldPrintAttributes.equals(mCurrPrintAttributes);
-    }
-
-    private class PrintController {
-        private final AtomicInteger mRequestCounter = new AtomicInteger();
-
-        private final RemotePrintDocumentAdapter mRemotePrintAdapter;
-
-        private final Bundle mMetadata;
-
-        private final ControllerHandler mHandler;
-
-        private final LayoutResultCallback mLayoutResultCallback;
-
-        private final WriteResultCallback mWriteResultCallback;
-
-        private int mControllerState = CONTROLLER_STATE_INITIALIZED;
-
-        private boolean mHasStarted;
-
-        private PageRange[] mRequestedPages;
-
-        public PrintController(RemotePrintDocumentAdapter adapter) {
-            mRemotePrintAdapter = adapter;
-            mMetadata = new Bundle();
-            mHandler = new ControllerHandler(getMainLooper());
-            mLayoutResultCallback = new LayoutResultCallback(mHandler);
-            mWriteResultCallback = new WriteResultCallback(mHandler);
-        }
-
-        public void initialize() {
-            mHasStarted = false;
-            mControllerState = CONTROLLER_STATE_INITIALIZED;
-        }
-
-        public void cancel() {
-            if (isWorking()) {
-                mRemotePrintAdapter.cancel();
-            }
-            mControllerState = CONTROLLER_STATE_CANCELLED;
-        }
-
-        public boolean isCancelled() {
-            return (mControllerState == CONTROLLER_STATE_CANCELLED);
-        }
-
-        public boolean isFinished() {
-            return (mControllerState == CONTROLLER_STATE_FINISHED);
-        }
-
-        public boolean hasStarted() {
-            return mHasStarted;
-        }
-
-        public boolean hasPerformedLayout() {
-            return mControllerState >= CONTROLLER_STATE_LAYOUT_COMPLETED;
-        }
-
-        public boolean isPerformingLayout() {
-            return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED;
-        }
-
-        public boolean isWorking() {
-            return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED
-                    || mControllerState == CONTROLLER_STATE_WRITE_STARTED;
-        }
-
-        public void start() {
-            mControllerState = CONTROLLER_STATE_STARTED;
-            mHasStarted = true;
-            mRemotePrintAdapter.start();
-        }
-
-        public void update() {
-            if (!mController.hasStarted()) {
-                mController.start();
-            }
-
-            // If the print attributes are the same and we are performing
-            // a layout, then we have to wait for it to completed which will
-            // trigger writing of the necessary pages.
-            final boolean printAttributesChanged = printAttributesChanged();
-            if (!printAttributesChanged && isPerformingLayout()) {
-                return;
-            }
-
-            // If print is confirmed we always do a layout since the previous
-            // ones were for preview and this one is for printing.
-            if (!printAttributesChanged && !mEditor.isPrintConfirmed()) {
-                if (mDocument.info == null) {
-                    // We are waiting for the result of a layout, so do nothing.
-                    return;
-                }
-                // If the attributes didn't change and we have done a layout, 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, mRequestCounter.get());
-            } else {
-                mSpoolerProvider.getSpooler().setPrintJobAttributesNoPersistence(
-                        mPrintJobId, mCurrPrintAttributes);
-
-                mMetadata.putBoolean(PrintDocumentAdapter.EXTRA_PRINT_PREVIEW,
-                        !mEditor.isPrintConfirmed());
-
-                mControllerState = CONTROLLER_STATE_LAYOUT_STARTED;
-
-                mRemotePrintAdapter.layout(mOldPrintAttributes, mCurrPrintAttributes,
-                        mLayoutResultCallback, mMetadata, mRequestCounter.incrementAndGet());
-
-                mOldPrintAttributes.copyFrom(mCurrPrintAttributes);
-            }
-        }
-
-        public void finish() {
-            mControllerState = CONTROLLER_STATE_FINISHED;
-            mRemotePrintAdapter.finish();
-        }
-
-        private void handleOnLayoutFinished(PrintDocumentInfo info,
-                boolean layoutChanged, int sequence) {
-            if (mRequestCounter.get() != sequence) {
-                return;
-            }
-
-            if (isCancelled()) {
-                mEditor.updateUi();
-                if (mEditor.isDone()) {
-                    PrintJobConfigActivity.this.finish();
-                }
-                return;
-            }
-
-            final int oldControllerState = mControllerState;
-
-            if (mControllerState == CONTROLLER_STATE_LAYOUT_STARTED) {
-                mControllerState = CONTROLLER_STATE_LAYOUT_COMPLETED;
-            }
-
-            // For layout purposes we care only whether the type or the page
-            // count changed. We still do not have the size since we did not
-            // call write. We use "layoutChanged" set by the application to
-            // know whether something else changed about the document.
-            final boolean infoChanged = !equalsIgnoreSize(info, mDocument.info);
-            // If the info changed, we update the document and the print job.
-            if (infoChanged) {
-                mDocument.info = info;
-                // Set the info.
-                mSpoolerProvider.getSpooler().setPrintJobPrintDocumentInfoNoPersistence(
-                        mPrintJobId, info);
-            }
-
-            // 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;
-                mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(
-                        mPrintJobId, null);
-            }
-
-            PageRange[] oldRequestedPages = mRequestedPages;
-
-            // 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 || mRequestedPages.length == 0) {
-                mEditor.updateUi();
-                if (mEditor.isDone()) {
-                    PrintJobConfigActivity.this.finish();
-                }
-                return;
-            } else {
-                // If print is not confirmed we just ask for the first of the
-                // selected pages to emulate a behavior that shows preview
-                // increasing the chances that apps will implement the APIs
-                // correctly.
-                if (!mEditor.isPrintConfirmed()) {
-                    if (ALL_PAGES_ARRAY.equals(mRequestedPages)) {
-                        mRequestedPages = new PageRange[] {new PageRange(0, 0)};
-                    } else {
-                        final int firstPage = mRequestedPages[0].getStart();
-                        mRequestedPages = new PageRange[] {new PageRange(firstPage, firstPage)};
-                    }
-                }
-            }
-
-            // If the info and the layout did not change...
-            if (!infoChanged && !layoutChanged
-                    // and we have the requested pages ... 
-                    && (PageRangeUtils.contains(mDocument.pages, mRequestedPages))
-                        // ...or the requested pages are being written...
-                        || (oldControllerState == CONTROLLER_STATE_WRITE_STARTED
-                            && oldRequestedPages != null
-                            && PageRangeUtils.contains(oldRequestedPages, mRequestedPages))) {
-                // Nothing interesting changed and we have all requested pages.
-                // Then update the print jobs's pages as we will not do a write
-                // and we usually update the pages in the write complete callback.
-                if (mDocument.pages != null) {
-                    // Update the print job's pages given we have them.
-                    updatePrintJobPages(mDocument.pages, mRequestedPages);
-                }
-                mEditor.updateUi();
-                if (mEditor.isDone()) {
-                    requestCreatePdfFileOrFinish();
-                }
-                return;
-            }
-
-            mEditor.updateUi();
-
-            // Request a write of the pages of interest.
-            mControllerState = CONTROLLER_STATE_WRITE_STARTED;
-            mRemotePrintAdapter.write(mRequestedPages, mWriteResultCallback,
-                    mRequestCounter.incrementAndGet());
-        }
-
-        private void handleOnLayoutFailed(final CharSequence error, int sequence) {
-            if (mRequestCounter.get() != sequence) {
-                return;
-            }
-            mControllerState = CONTROLLER_STATE_FAILED;
-            mEditor.showUi(Editor.UI_ERROR, new Runnable() {
-                @Override
-                public void run() {
-                    if (!TextUtils.isEmpty(error)) {
-                        TextView messageView = (TextView) findViewById(R.id.message);
-                        messageView.setText(error);
-                    }
-                }
-            });
-        }
-
-        private void handleOnWriteFinished(PageRange[] pages, int sequence) {
-            if (mRequestCounter.get() != sequence) {
-                return;
-            }
-
-            if (isCancelled()) {
-                if (mEditor.isDone()) {
-                    PrintJobConfigActivity.this.finish();
-                }
-                return;
-            }
-
-            mControllerState = CONTROLLER_STATE_WRITE_COMPLETED;
-
-            // Update the document size.
-            File file = mSpoolerProvider.getSpooler()
-                    .generateFileForPrintJob(mPrintJobId);
-            mDocument.info.setDataSize(file.length());
-
-            // Update the print job with the updated info.
-            mSpoolerProvider.getSpooler().setPrintJobPrintDocumentInfoNoPersistence(
-                    mPrintJobId, mDocument.info);
-
-            // Update which pages we have fetched.
-            mDocument.pages = PageRangeUtils.normalize(pages);
-
-            if (DEBUG) {
-                Log.i(LOG_TAG, "Requested: " + Arrays.toString(mRequestedPages)
-                        + " and got: " + Arrays.toString(mDocument.pages));
-            }
-
-            updatePrintJobPages(mDocument.pages, mRequestedPages);
-
-            if (mEditor.isDone()) {
-                requestCreatePdfFileOrFinish();
-            }
-        }
-
-        private void updatePrintJobPages(PageRange[] writtenPages, PageRange[] requestedPages) {
-            // Adjust the print job pages based on what was requested and written.
-            // The cases are ordered in the most expected to the least expected.
-            if (Arrays.equals(writtenPages, requestedPages)) {
-                // We got a document with exactly the pages we wanted. Hence,
-                // the printer has to print all pages in the data.
-                mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
-                        ALL_PAGES_ARRAY);
-            } else if (Arrays.equals(writtenPages, ALL_PAGES_ARRAY)) {
-                // We requested specific pages but got all of them. Hence,
-                // the printer has to print only the requested pages.
-                mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
-                        requestedPages);
-            } else if (PageRangeUtils.contains(writtenPages, requestedPages)) {
-                // We requested specific pages and got more but not all pages.
-                // Hence, we have to offset appropriately the printed pages to
-                // be based off the start of the written ones instead of zero.
-                // The written pages are always non-null and not empty.
-                final int offset = -writtenPages[0].getStart();
-                PageRange[] offsetPages = Arrays.copyOf(requestedPages, requestedPages.length);
-                PageRangeUtils.offset(offsetPages, offset);
-                mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
-                        offsetPages);
-            } else if (Arrays.equals(requestedPages, ALL_PAGES_ARRAY)
-                    && writtenPages.length == 1 && writtenPages[0].getStart() == 0
-                    && writtenPages[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.
-                mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
-                        writtenPages);
-            } else {
-                // We did not get the pages we requested, then the application
-                // misbehaves, so we fail quickly.
-                mControllerState = CONTROLLER_STATE_FAILED;
-                Log.e(LOG_TAG, "Received invalid pages from the app: requested="
-                        + Arrays.toString(requestedPages) + " written="
-                        + Arrays.toString(writtenPages));
-                mEditor.showUi(Editor.UI_ERROR, null);
-            }
-        }
-
-        private void requestCreatePdfFileOrFinish() {
-            if (mEditor.isPrintingToPdf()) {
-                Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
-                intent.setType("application/pdf");
-                intent.putExtra(Intent.EXTRA_TITLE, mDocument.info.getName());
-                intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName);
-                startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
-            } else {
-                PrintJobConfigActivity.this.finish();
-            }
-        }
-
-        private void handleOnWriteFailed(final CharSequence error, int sequence) {
-            if (mRequestCounter.get() != sequence) {
-                return;
-            }
-            mControllerState = CONTROLLER_STATE_FAILED;
-            mEditor.showUi(Editor.UI_ERROR, new Runnable() {
-                @Override
-                public void run() {
-                    if (!TextUtils.isEmpty(error)) {
-                        TextView messageView = (TextView) findViewById(R.id.message);
-                        messageView.setText(error);
-                    }
-                }
-            });
-        }
-
-        private boolean equalsIgnoreSize(PrintDocumentInfo lhs, PrintDocumentInfo rhs) {
-            if (lhs == rhs) {
-                return true;
-            }
-            if (lhs == null) {
-                if (rhs != null) {
-                    return false;
-                }
-            } else {
-                if (rhs == null) {
-                    return false;
-                }
-                if (lhs.getContentType() != rhs.getContentType()
-                        || lhs.getPageCount() != rhs.getPageCount()) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        private final class ControllerHandler 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 ControllerHandler(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);
-                        final int sequence = message.arg2;
-                        handleOnLayoutFinished(info, changed, sequence);
-                    } break;
-
-                    case MSG_ON_LAYOUT_FAILED: {
-                        CharSequence error = (CharSequence) message.obj;
-                        final int sequence = message.arg1;
-                        handleOnLayoutFailed(error, sequence);
-                    } break;
-
-                    case MSG_ON_WRITE_FINISHED: {
-                        PageRange[] pages = (PageRange[]) message.obj;
-                        final int sequence = message.arg1;
-                        handleOnWriteFinished(pages, sequence);
-                    } break;
-
-                    case MSG_ON_WRITE_FAILED: {
-                        CharSequence error = (CharSequence) message.obj;
-                        final int sequence = message.arg1;
-                        handleOnWriteFailed(error, sequence);
-                    } break;
-                }
-            }
-        }
-    }
-
-    private static final class LayoutResultCallback extends ILayoutResultCallback.Stub {
-        private final WeakReference<PrintController.ControllerHandler> mWeakHandler;
-
-        public LayoutResultCallback(PrintController.ControllerHandler handler) {
-            mWeakHandler = new WeakReference<PrintController.ControllerHandler>(handler);
-        }
-
-        @Override
-        public void onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence) {
-            Handler handler = mWeakHandler.get();
-            if (handler != null) {
-                handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_LAYOUT_FINISHED,
-                        changed ? 1 : 0, sequence, info).sendToTarget();
-            }
-        }
-
-        @Override
-        public void onLayoutFailed(CharSequence error, int sequence) {
-            Handler handler = mWeakHandler.get();
-            if (handler != null) {
-                handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_LAYOUT_FAILED,
-                        sequence, 0, error).sendToTarget();
-            }
-        }
-    }
-
-    private static final class WriteResultCallback extends IWriteResultCallback.Stub {
-        private final WeakReference<PrintController.ControllerHandler> mWeakHandler;
-
-        public WriteResultCallback(PrintController.ControllerHandler handler) {
-            mWeakHandler = new WeakReference<PrintController.ControllerHandler>(handler);
-        }
-
-        @Override
-        public void onWriteFinished(PageRange[] pages, int sequence) {
-            Handler handler = mWeakHandler.get();
-            if (handler != null) {
-                handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_WRITE_FINISHED,
-                        sequence, 0, pages).sendToTarget();
-            }
-        }
-
-        @Override
-        public void onWriteFailed(CharSequence error, int sequence) {
-            Handler handler = mWeakHandler.get();
-            if (handler != null) {
-                handler.obtainMessage(PrintController.ControllerHandler.MSG_ON_WRITE_FAILED,
-                    sequence, 0, error).sendToTarget();
-            }
-        }
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        switch (requestCode) {
-            case ACTIVITY_REQUEST_CREATE_FILE: {
-                if (data != null) {
-                    Uri uri = data.getData();
-                    writePrintJobDataAndFinish(uri);
-                } else {
-                    mEditor.showUi(Editor.UI_EDITING_PRINT_JOB,
-                            new Runnable() {
-                        @Override
-                        public void run() {
-                            mEditor.initialize();
-                            mEditor.bindUi();
-                            mEditor.reselectCurrentPrinter();
-                            mEditor.updateUi();
-                        }
-                    });
-                }
-            } break;
-
-            case ACTIVITY_REQUEST_SELECT_PRINTER: {
-                if (resultCode == RESULT_OK) {
-                    PrinterId printerId = (PrinterId) data.getParcelableExtra(
-                            INTENT_EXTRA_PRINTER_ID);
-                    if (printerId != null) {
-                        mEditor.ensurePrinterSelected(printerId);
-                        break;
-                    }
-                }
-                mEditor.ensureCurrentPrinterSelected();
-            } break;
-
-            case ACTIVITY_POPULATE_ADVANCED_PRINT_OPTIONS: {
-                if (resultCode == RESULT_OK) {
-                    PrintJobInfo printJobInfo = (PrintJobInfo) data.getParcelableExtra(
-                            PrintService.EXTRA_PRINT_JOB_INFO);
-                    if (printJobInfo != null) {
-                        mEditor.updateFromAdvancedOptions(printJobInfo);
-                        break;
-                    }
-                }
-                mEditor.cancel();
-                PrintJobConfigActivity.this.finish();
-            } break;
-        }
-    }
-
-    private void writePrintJobDataAndFinish(final Uri uri) {
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                InputStream in = null;
-                OutputStream out = null;
-                try {
-                    PrintJobInfo printJob = mSpoolerProvider.getSpooler()
-                            .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY);
-                    if (printJob == null) {
-                        return null;
-                    }
-                    File file = mSpoolerProvider.getSpooler()
-                            .generateFileForPrintJob(mPrintJobId);
-                    in = new FileInputStream(file);
-                    out = getContentResolver().openOutputStream(uri);
-                    final byte[] buffer = new byte[8192];
-                    while (true) {
-                        final int readByteCount = in.read(buffer);
-                        if (readByteCount < 0) {
-                            break;
-                        }
-                        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);
-                }
-                return null;
-            }
-
-            @Override
-            public void onPostExecute(Void result) {
-                mEditor.cancel();
-                PrintJobConfigActivity.this.finish();
-            }
-        }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
-    }
-
-    private final class Editor {
-        private static final int UI_NONE = 0;
-        private static final int UI_EDITING_PRINT_JOB = 1;
-        private static final int UI_GENERATING_PRINT_JOB = 2;
-        private static final int UI_ERROR = 3;
-
-        private EditText mCopiesEditText;
-
-        private TextView mRangeOptionsTitle;
-        private TextView mPageRangeTitle;
-        private EditText mPageRangeEditText;
-
-        private Spinner mDestinationSpinner;
-        private DestinationAdapter mDestinationSpinnerAdapter;
-
-        private Spinner mMediaSizeSpinner;
-        private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
-
-        private Spinner mColorModeSpinner;
-        private ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
-
-        private Spinner mOrientationSpinner;
-        private  ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
-
-        private Spinner mRangeOptionsSpinner;
-        private ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter;
-
-        private SimpleStringSplitter mStringCommaSplitter =
-                new SimpleStringSplitter(',');
-
-        private View mContentContainer;
-
-        private View mAdvancedPrintOptionsContainer;
-
-        private Button mAdvancedOptionsButton;
-
-        private Button mPrintButton;
-
-        private PrinterId mNextPrinterId;
-
-        private PrinterInfo mCurrentPrinter;
-
-        private MediaSizeComparator mMediaSizeComparator;
-
-        private final OnFocusChangeListener mFocusListener = new OnFocusChangeListener() {
-            @Override
-            public void onFocusChange(View view, boolean hasFocus) {
-                EditText editText = (EditText) view;
-                if (!TextUtils.isEmpty(editText.getText())) {
-                    editText.setSelection(editText.getText().length());
-                }
-            }
-        };
-
-        private final OnItemSelectedListener mOnItemSelectedListener =
-                new AdapterView.OnItemSelectedListener() {
-            @Override
-            public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
-                if (spinner == mDestinationSpinner) {
-                    if (mIgnoreNextDestinationChange) {
-                        mIgnoreNextDestinationChange = false;
-                        return;
-                    }
-
-                    if (position == AdapterView.INVALID_POSITION) {
-                        updateUi();
-                        return;
-                    }
-
-                    if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) {
-                        startSelectPrinterActivity();
-                        return;
-                    }
-
-                    mCapabilitiesTimeout.remove();
-
-                    mCurrentPrinter = (PrinterInfo) mDestinationSpinnerAdapter
-                            .getItem(position);
-
-                    mSpoolerProvider.getSpooler().setPrintJobPrinterNoPersistence(
-                            mPrintJobId, mCurrentPrinter);
-
-                    if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
-                        mCapabilitiesTimeout.post();
-                        updateUi();
-                        return;
-                    }
-
-                    PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
-                    if (capabilities == null) {
-                        mCapabilitiesTimeout.post();
-                        updateUi();
-                        refreshCurrentPrinter();
-                    } else {
-                        updatePrintAttributes(capabilities);
-                        updateUi();
-                        mController.update();
-                        refreshCurrentPrinter();
-                    }
-                } else if (spinner == mMediaSizeSpinner) {
-                    if (mIgnoreNextMediaSizeChange) {
-                        mIgnoreNextMediaSizeChange = false;
-                        return;
-                    }
-                    if (mOldMediaSizeSelectionIndex
-                            == mMediaSizeSpinner.getSelectedItemPosition()) {
-                        mOldMediaSizeSelectionIndex = AdapterView.INVALID_POSITION;
-                        return;
-                    }
-                    SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
-                    if (mOrientationSpinner.getSelectedItemPosition() == 0) {
-                        mCurrPrintAttributes.setMediaSize(mediaItem.value.asPortrait());
-                    } else {
-                        mCurrPrintAttributes.setMediaSize(mediaItem.value.asLandscape());
-                    }
-                    if (!hasErrors()) {
-                        mController.update();
-                    }
-                } else if (spinner == mColorModeSpinner) {
-                    if (mIgnoreNextColorChange) {
-                        mIgnoreNextColorChange = false;
-                        return;
-                    }
-                    if (mOldColorModeSelectionIndex
-                            == mColorModeSpinner.getSelectedItemPosition()) {
-                        mOldColorModeSelectionIndex = AdapterView.INVALID_POSITION;
-                        return;
-                    }
-                    SpinnerItem<Integer> colorModeItem =
-                            mColorModeSpinnerAdapter.getItem(position);
-                    mCurrPrintAttributes.setColorMode(colorModeItem.value);
-                    if (!hasErrors()) {
-                        mController.update();
-                    }
-                } else if (spinner == mOrientationSpinner) {
-                    if (mIgnoreNextOrientationChange) {
-                        mIgnoreNextOrientationChange = false;
-                        return;
-                    }
-                    SpinnerItem<Integer> orientationItem =
-                            mOrientationSpinnerAdapter.getItem(position);
-                    setCurrentPrintAttributesOrientation(orientationItem.value);
-                    if (!hasErrors()) {
-                        mController.update();
-                    }
-                } else if (spinner == mRangeOptionsSpinner) {
-                    if (mIgnoreNextRangeOptionChange) {
-                        mIgnoreNextRangeOptionChange = false;
-                        return;
-                    }
-                    updateUi();
-                    if (!hasErrors()) {
-                        mController.update();
-                    }
-                }
-            }
-
-            @Override
-            public void onNothingSelected(AdapterView<?> parent) {
-                /* do nothing*/
-            }
-        };
-
-        private void setCurrentPrintAttributesOrientation(int orientation) {
-            MediaSize mediaSize = mCurrPrintAttributes.getMediaSize();
-            if (orientation == ORIENTATION_PORTRAIT) {
-                if (!mediaSize.isPortrait()) {
-                    // Rotate the media size.
-                    mCurrPrintAttributes.setMediaSize(mediaSize.asPortrait());
-
-                    // Rotate the resolution.
-                    Resolution oldResolution = mCurrPrintAttributes.getResolution();
-                    Resolution newResolution = new Resolution(
-                            oldResolution.getId(),
-                            oldResolution.getLabel(),
-                            oldResolution.getVerticalDpi(),
-                            oldResolution.getHorizontalDpi());
-                    mCurrPrintAttributes.setResolution(newResolution);
-
-                    // Rotate the physical margins.
-                    Margins oldMinMargins = mCurrPrintAttributes.getMinMargins();
-                    Margins newMinMargins = new Margins(
-                            oldMinMargins.getBottomMils(),
-                            oldMinMargins.getLeftMils(),
-                            oldMinMargins.getTopMils(),
-                            oldMinMargins.getRightMils());
-                    mCurrPrintAttributes.setMinMargins(newMinMargins);
-                }
-            } else {
-                if (mediaSize.isPortrait()) {
-                    // Rotate the media size.
-                    mCurrPrintAttributes.setMediaSize(mediaSize.asLandscape());
-
-                    // Rotate the resolution.
-                    Resolution oldResolution = mCurrPrintAttributes.getResolution();
-                    Resolution newResolution = new Resolution(
-                            oldResolution.getId(),
-                            oldResolution.getLabel(),
-                            oldResolution.getVerticalDpi(),
-                            oldResolution.getHorizontalDpi());
-                    mCurrPrintAttributes.setResolution(newResolution);
-
-                    // Rotate the physical margins.
-                    Margins oldMinMargins = mCurrPrintAttributes.getMinMargins();
-                    Margins newMargins = new Margins(
-                            oldMinMargins.getTopMils(),
-                            oldMinMargins.getRightMils(),
-                            oldMinMargins.getBottomMils(),
-                            oldMinMargins.getLeftMils());
-                    mCurrPrintAttributes.setMinMargins(newMargins);
-                }
-            }
-        }
-
-        private void updatePrintAttributes(PrinterCapabilitiesInfo capabilities) {
-            PrintAttributes defaults = capabilities.getDefaults();
-
-            // Sort the media sizes based on the current locale.
-            List<MediaSize> sortedMediaSizes = new ArrayList<MediaSize>(
-                    capabilities.getMediaSizes());
-            Collections.sort(sortedMediaSizes, mMediaSizeComparator);
-
-            // Media size.
-            MediaSize currMediaSize = mCurrPrintAttributes.getMediaSize();
-            if (currMediaSize == null) {
-                mCurrPrintAttributes.setMediaSize(defaults.getMediaSize());
-            } else {
-                MediaSize currMediaSizePortrait = currMediaSize.asPortrait();
-                final int mediaSizeCount = sortedMediaSizes.size();
-                for (int i = 0; i < mediaSizeCount; i++) {
-                    MediaSize mediaSize = sortedMediaSizes.get(i);
-                    if (currMediaSizePortrait.equals(mediaSize.asPortrait())) {
-                        mCurrPrintAttributes.setMediaSize(currMediaSize);
-                        break;
-                    }
-                }
-            }
-
-            // Color mode.
-            final int colorMode = mCurrPrintAttributes.getColorMode();
-            if ((capabilities.getColorModes() & colorMode) == 0) {
-                mCurrPrintAttributes.setColorMode(colorMode);
-            }
-
-            // Resolution
-            Resolution resolution = mCurrPrintAttributes.getResolution();
-            if (resolution == null || !capabilities.getResolutions().contains(resolution)) {
-                mCurrPrintAttributes.setResolution(defaults.getResolution());
-            }
-
-            // Margins.
-            Margins margins = mCurrPrintAttributes.getMinMargins();
-            if (margins == null) {
-                mCurrPrintAttributes.setMinMargins(defaults.getMinMargins());
-            } else {
-                Margins minMargins = capabilities.getMinMargins();
-                if (margins.getLeftMils() < minMargins.getLeftMils()
-                        || margins.getTopMils() < minMargins.getTopMils()
-                        || margins.getRightMils() > minMargins.getRightMils()
-                        || margins.getBottomMils() > minMargins.getBottomMils()) {
-                    mCurrPrintAttributes.setMinMargins(defaults.getMinMargins());
-                }
-            }
-        }
-
-        private final TextWatcher mCopiesTextWatcher = new TextWatcher() {
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-                /* do nothing */
-            }
-
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-                /* do nothing */
-            }
-
-            @Override
-            public void afterTextChanged(Editable editable) {
-                if (mIgnoreNextCopiesChange) {
-                    mIgnoreNextCopiesChange = false;
-                    return;
-                }
-
-                final boolean hadErrors = hasErrors();
-
-                if (editable.length() == 0) {
-                    mCopiesEditText.setError("");
-                    updateUi();
-                    return;
-                }
-
-                int copies = 0;
-                try {
-                    copies = Integer.parseInt(editable.toString());
-                } catch (NumberFormatException nfe) {
-                    /* ignore */
-                }
-
-                if (copies < MIN_COPIES) {
-                    mCopiesEditText.setError("");
-                    updateUi();
-                    return;
-                }
-
-                mCopiesEditText.setError(null);
-                mSpoolerProvider.getSpooler().setPrintJobCopiesNoPersistence(
-                        mPrintJobId, copies);
-                updateUi();
-
-                if (hadErrors && !hasErrors() && printAttributesChanged()) {
-                    mController.update();
-                }
-            }
-        };
-
-        private final TextWatcher mRangeTextWatcher = new TextWatcher() {
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-                /* do nothing */
-            }
-
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-                /* do nothing */
-            }
-
-            @Override
-            public void afterTextChanged(Editable editable) {
-                if (mIgnoreNextRangeChange) {
-                    mIgnoreNextRangeChange = false;
-                    return;
-                }
-
-                final boolean hadErrors = hasErrors();
-
-                String text = editable.toString();
-
-                if (TextUtils.isEmpty(text)) {
-                    mPageRangeEditText.setError("");
-                    updateUi();
-                    return;
-                }
-
-                String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////");
-                if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) {
-                    mPageRangeEditText.setError("");
-                    updateUi();
-                    return;
-                }
-
-                // The range
-                Matcher matcher = PATTERN_DIGITS.matcher(text);
-                while (matcher.find()) {
-                    String numericString = text.substring(matcher.start(), matcher.end()).trim();
-                    if (TextUtils.isEmpty(numericString)) {
-                        continue;
-                    }
-                    final int pageIndex = Integer.parseInt(numericString);
-                    if (pageIndex < 1 || pageIndex > mDocument.info.getPageCount()) {
-                        mPageRangeEditText.setError("");
-                        updateUi();
-                        return;
-                    }
-                }
-
-                // We intentionally do not catch the case of the from page being
-                // greater than the to page. When computing the requested pages
-                // we just swap them if necessary.
-
-                // Keep the print job up to date with the selected pages if we
-                // know how many pages are there in the document.
-                PageRange[] requestedPages = getRequestedPages();
-                if (requestedPages != null && requestedPages.length > 0
-                        && requestedPages[requestedPages.length - 1].getEnd()
-                                < mDocument.info.getPageCount()) {
-                    mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(
-                            mPrintJobId, requestedPages);
-                }
-
-                mPageRangeEditText.setError(null);
-                mPrintButton.setEnabled(true);
-                updateUi();
-
-                if (hadErrors && !hasErrors() && printAttributesChanged()) {
-                    updateUi();
-                }
-            }
-        };
-
-        private final WaitForPrinterCapabilitiesTimeout mCapabilitiesTimeout =
-                new WaitForPrinterCapabilitiesTimeout();
-
-        private int mEditorState;
-
-        private boolean mIgnoreNextDestinationChange;
-        private int mOldMediaSizeSelectionIndex;
-        private int mOldColorModeSelectionIndex;
-        private boolean mIgnoreNextOrientationChange;
-        private boolean mIgnoreNextRangeOptionChange;
-        private boolean mIgnoreNextCopiesChange;
-        private boolean mIgnoreNextRangeChange;
-        private boolean mIgnoreNextMediaSizeChange;
-        private boolean mIgnoreNextColorChange;
-
-        private int mCurrentUi = UI_NONE;
-
-        private boolean mFavoritePrinterSelected;
-
-        public Editor() {
-            showUi(UI_EDITING_PRINT_JOB, null);
-        }
-
-        public void postCreate() {
-            // Destination.
-            mMediaSizeComparator = new MediaSizeComparator(PrintJobConfigActivity.this);
-            mDestinationSpinnerAdapter = new DestinationAdapter();
-            mDestinationSpinnerAdapter.registerDataSetObserver(new DataSetObserver() {
-                @Override
-                public void onChanged() {
-                    // Initially, we have only safe to PDF as a printer but after some
-                    // printers are loaded we want to select the user's favorite one
-                    // which is the first.
-                    if (!mFavoritePrinterSelected && mDestinationSpinnerAdapter.getCount() > 1) {
-                        mFavoritePrinterSelected = true;
-                        mDestinationSpinner.setSelection(0);
-                        // Workaround again the weird spinner behavior to notify for selection
-                        // change on the next layout pass as the current printer is used below.
-                        mCurrentPrinter = (PrinterInfo) mDestinationSpinnerAdapter.getItem(0);
-                    }
-
-                    // If there is a next printer to select and we succeed selecting
-                    // it - done. Let the selection handling code make everything right.
-                    if (mNextPrinterId != null && selectPrinter(mNextPrinterId)) {
-                        mNextPrinterId = null;
-                        return;
-                    }
-
-                    // If the current printer properties changed, we update the UI.
-                    if (mCurrentPrinter != null) {
-                        final int printerCount = mDestinationSpinnerAdapter.getCount();
-                        for (int i = 0; i < printerCount; i++) {
-                            Object item = mDestinationSpinnerAdapter.getItem(i);
-                            // Some items are not printers
-                            if (item instanceof PrinterInfo) {
-                                PrinterInfo printer = (PrinterInfo) item;
-                                if (!printer.getId().equals(mCurrentPrinter.getId())) {
-                                    continue;
-                                }
-
-                                // If nothing changed - done.
-                                if (mCurrentPrinter.equals(printer)) {
-                                    return;
-                                }
-
-                                // If the current printer became available and has no
-                                // capabilities, we refresh it.
-                                if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE
-                                        && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
-                                        && printer.getCapabilities() == null) {
-                                    if (!mCapabilitiesTimeout.isPosted()) {
-                                        mCapabilitiesTimeout.post();
-                                    }
-                                    mCurrentPrinter.copyFrom(printer);
-                                    refreshCurrentPrinter();
-                                    return;
-                                }
-
-                                // If the current printer became unavailable or its
-                                // capabilities go away, we update the UI and add a
-                                // timeout to declare the printer as unavailable.
-                                if ((mCurrentPrinter.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
-                                        && printer.getStatus() == PrinterInfo.STATUS_UNAVAILABLE)
-                                    || (mCurrentPrinter.getCapabilities() != null
-                                        && printer.getCapabilities() == null)) {
-                                    if (!mCapabilitiesTimeout.isPosted()) {
-                                        mCapabilitiesTimeout.post();
-                                    }
-                                    mCurrentPrinter.copyFrom(printer);
-                                    updateUi();
-                                    return;
-                                }
-
-                                // We just refreshed the current printer.
-                                if (printer.getCapabilities() != null
-                                        && mCapabilitiesTimeout.isPosted()) {
-                                    mCapabilitiesTimeout.remove();
-                                    updatePrintAttributes(printer.getCapabilities());
-                                    updateUi();
-                                    mController.update();
-                                }
-
-                                // Update the UI if capabilities changed.
-                                boolean capabilitiesChanged = false;
-
-                                if (mCurrentPrinter.getCapabilities() == null) {
-                                    if (printer.getCapabilities() != null) {
-                                        updatePrintAttributes(printer.getCapabilities());
-                                        capabilitiesChanged = true;
-                                    }
-                                } else if (!mCurrentPrinter.getCapabilities().equals(
-                                        printer.getCapabilities())) {
-                                    capabilitiesChanged = true;
-                                }
-
-                                // Update the UI if the status changed.
-                                final boolean statusChanged = mCurrentPrinter.getStatus()
-                                        != printer.getStatus();
-
-                                // Update the printer with the latest info.
-                                if (!mCurrentPrinter.equals(printer)) {
-                                    mCurrentPrinter.copyFrom(printer);
-                                }
-
-                                if (capabilitiesChanged || statusChanged) {
-                                    // If something changed during update...
-                                    if (updateUi() || !mController.hasPerformedLayout()) {
-                                        // Update the document.
-                                        mController.update();
-                                    }
-                                }
-
-                                break;
-                            }
-                        }
-                    }
-                }
-
-                @Override
-                public void onInvalidated() {
-                    /* do nothing - we always have one fake PDF printer */
-                }
-            });
-
-            // Media size.
-            mMediaSizeSpinnerAdapter = new ArrayAdapter<SpinnerItem<MediaSize>>(
-                    PrintJobConfigActivity.this,
-                    R.layout.spinner_dropdown_item, R.id.title);
-
-            // Color mode.
-            mColorModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
-                    PrintJobConfigActivity.this,
-                    R.layout.spinner_dropdown_item, R.id.title);
-
-            // Orientation
-            mOrientationSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
-                    PrintJobConfigActivity.this,
-                    R.layout.spinner_dropdown_item, R.id.title);
-            String[] orientationLabels = getResources().getStringArray(
-                  R.array.orientation_labels);
-            mOrientationSpinnerAdapter.add(new SpinnerItem<Integer>(
-                    ORIENTATION_PORTRAIT, orientationLabels[0]));
-            mOrientationSpinnerAdapter.add(new SpinnerItem<Integer>(
-                    ORIENTATION_LANDSCAPE, orientationLabels[1]));
-
-            // Range options
-            mRangeOptionsSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
-                    PrintJobConfigActivity.this,
-                    R.layout.spinner_dropdown_item, R.id.title);
-            final int[] rangeOptionsValues = getResources().getIntArray(
-                    R.array.page_options_values);
-            String[] rangeOptionsLabels = getResources().getStringArray(
-                    R.array.page_options_labels);
-            final int rangeOptionsCount = rangeOptionsLabels.length;
-            for (int i = 0; i < rangeOptionsCount; i++) {
-                mRangeOptionsSpinnerAdapter.add(new SpinnerItem<Integer>(
-                        rangeOptionsValues[i], rangeOptionsLabels[i]));
-            }
-
-            showUi(UI_EDITING_PRINT_JOB, null);
-            bindUi();
-            updateUi();
-        }
-
-        public void reselectCurrentPrinter() {
-            if (mCurrentPrinter != null) {
-                // TODO: While the data did not change and we set the adapter
-                // to a newly inflated spinner, the latter does not show the
-                // current item unless we poke the adapter. This requires more
-                // investigation. Maybe an optimization in AdapterView does not
-                // call into the adapter if the view is not visible which is the
-                // case when we set the adapter.
-                mDestinationSpinnerAdapter.notifyDataSetChanged();
-                final int position = mDestinationSpinnerAdapter.getPrinterIndex(
-                        mCurrentPrinter.getId());
-                mDestinationSpinner.setSelection(position);
-            }
-        }
-
-        public void refreshCurrentPrinter() {
-            PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
-            if (printer != null) {
-                FusedPrintersProvider printersLoader = (FusedPrintersProvider)
-                        (Loader<?>) getLoaderManager().getLoader(
-                                LOADER_ID_PRINTERS_LOADER);
-                if (printersLoader != null) {
-                    printersLoader.setTrackedPrinter(printer.getId());
-                }
-            }
-        }
-
-        public void addCurrentPrinterToHistory() {
-            PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
-            PrinterId fakePdfPritnerId = mDestinationSpinnerAdapter.mFakePdfPrinter.getId();
-            if (printer != null && !printer.getId().equals(fakePdfPritnerId)) {
-                FusedPrintersProvider printersLoader = (FusedPrintersProvider)
-                        (Loader<?>) getLoaderManager().getLoader(
-                                LOADER_ID_PRINTERS_LOADER);
-                if (printersLoader != null) {
-                    printersLoader.addHistoricalPrinter(printer);
-                }
-            }
-        }
-
-        public void updateFromAdvancedOptions(PrintJobInfo printJobInfo) {
-            boolean updateContent = false;
-
-            // Copies.
-            mCopiesEditText.setText(String.valueOf(printJobInfo.getCopies()));
-
-            // Media size and orientation
-            PrintAttributes attributes = printJobInfo.getAttributes();
-            if (!mCurrPrintAttributes.getMediaSize().equals(attributes.getMediaSize())) {
-                final int mediaSizeCount = mMediaSizeSpinnerAdapter.getCount();
-                for (int i = 0; i < mediaSizeCount; i++) {
-                    MediaSize mediaSize = mMediaSizeSpinnerAdapter.getItem(i).value;
-                    if (mediaSize.asPortrait().equals(attributes.getMediaSize().asPortrait())) {
-                        updateContent = true;
-                        mCurrPrintAttributes.setMediaSize(attributes.getMediaSize());
-                        mMediaSizeSpinner.setSelection(i);
-                        mIgnoreNextMediaSizeChange = true;
-                        if (attributes.getMediaSize().isPortrait()) {
-                            mOrientationSpinner.setSelection(0);
-                            mIgnoreNextOrientationChange = true;
-                        } else {
-                            mOrientationSpinner.setSelection(1);
-                            mIgnoreNextOrientationChange = true;
-                        }
-                        break;
-                    }
-                }
-            }
-
-            // Color mode.
-            final int colorMode = attributes.getColorMode();
-            if (mCurrPrintAttributes.getColorMode() != colorMode) {
-                if (colorMode == PrintAttributes.COLOR_MODE_MONOCHROME) {
-                    updateContent = true;
-                    mColorModeSpinner.setSelection(0);
-                    mIgnoreNextColorChange = true;
-                    mCurrPrintAttributes.setColorMode(attributes.getColorMode());
-                } else if (colorMode == PrintAttributes.COLOR_MODE_COLOR) {
-                    updateContent = true;
-                    mColorModeSpinner.setSelection(1);
-                    mIgnoreNextColorChange = true;
-                    mCurrPrintAttributes.setColorMode(attributes.getColorMode());
-                }
-            }
-
-            // Range.
-            PageRange[] pageRanges = printJobInfo.getPages();
-            if (pageRanges != null && pageRanges.length > 0) {
-                pageRanges = PageRangeUtils.normalize(pageRanges);
-                final int pageRangeCount = pageRanges.length;
-                if (pageRangeCount == 1 && pageRanges[0] == PageRange.ALL_PAGES) {
-                    mRangeOptionsSpinner.setSelection(0);
-                } else {
-                    final int pageCount = mDocument.info.getPageCount();
-                    if (pageRanges[0].getStart() >= 0
-                            && pageRanges[pageRanges.length - 1].getEnd() < pageCount) {
-                        mRangeOptionsSpinner.setSelection(1);
-                        StringBuilder builder = new StringBuilder();
-                        for (int i = 0; i < pageRangeCount; i++) {
-                            if (builder.length() > 0) {
-                                builder.append(',');
-                            }
-                            PageRange pageRange = pageRanges[i];
-                            final int shownStartPage = pageRange.getStart() + 1;
-                            final int shownEndPage = pageRange.getEnd() + 1;
-                            builder.append(shownStartPage);
-                            if (shownStartPage != shownEndPage) {
-                                builder.append('-');
-                                builder.append(shownEndPage);
-                            }
-                        }
-                        mPageRangeEditText.setText(builder.toString());
-                    }
-                }
-            }
-
-            // Update the advanced options.
-            mSpoolerProvider.getSpooler().setPrintJobAdvancedOptionsNoPersistence(
-                    mPrintJobId, printJobInfo.getAdvancedOptions());
-
-            // Update the content if needed.
-            if (updateContent) {
-                mController.update();
-            }
-        }
-
-        public void ensurePrinterSelected(PrinterId printerId) {
-            // If the printer is not present maybe the loader is not
-            // updated yet. In this case make a note and as soon as
-            // the printer appears will will select it.
-            if (!selectPrinter(printerId)) {
-                mNextPrinterId = printerId;
-            }
-        }
-
-        public boolean selectPrinter(PrinterId printerId) {
-            mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerId);
-            final int position = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
-            if (position != AdapterView.INVALID_POSITION
-                    && position != mDestinationSpinner.getSelectedItemPosition()) {
-                Object item = mDestinationSpinnerAdapter.getItem(position);
-                mCurrentPrinter = (PrinterInfo) item;
-                mDestinationSpinner.setSelection(position);
-                return true;
-            }
-            return false;
-        }
-
-        public void ensureCurrentPrinterSelected() {
-            if (mCurrentPrinter != null) {
-                selectPrinter(mCurrentPrinter.getId());
-            }
-        }
-
-        public boolean isPrintingToPdf() {
-            return mDestinationSpinner.getSelectedItem()
-                    == mDestinationSpinnerAdapter.mFakePdfPrinter;
-        }
-
-        public boolean shouldCloseOnTouch(MotionEvent event) {
-            if (event.getAction() != MotionEvent.ACTION_DOWN) {
-                return false;
-            }
-
-            final int[] locationInWindow = new int[2];
-            mContentContainer.getLocationInWindow(locationInWindow);
-
-            final int windowTouchSlop = ViewConfiguration.get(PrintJobConfigActivity.this)
-                    .getScaledWindowTouchSlop();
-            final int eventX = (int) event.getX();
-            final int eventY = (int) event.getY();
-            final int lenientWindowLeft = locationInWindow[0] - windowTouchSlop;
-            final int lenientWindowRight = lenientWindowLeft + mContentContainer.getWidth()
-                    + windowTouchSlop;
-            final int lenientWindowTop = locationInWindow[1] - windowTouchSlop;
-            final int lenientWindowBottom = lenientWindowTop + mContentContainer.getHeight()
-                    + windowTouchSlop;
-
-            if (eventX < lenientWindowLeft || eventX > lenientWindowRight
-                    || eventY < lenientWindowTop || eventY > lenientWindowBottom) {
-                return true;
-            }
-            return false;
-        }
-
-        public boolean isShwoingGeneratingPrintJobUi() {
-            return (mCurrentUi == UI_GENERATING_PRINT_JOB);
-        }
-
-        public void showUi(int ui, final Runnable postSwitchCallback) {
-            if (ui == UI_NONE) {
-                throw new IllegalStateException("cannot remove the ui");
-            }
-
-            if (mCurrentUi == ui) {
-                return;
-            }
-
-            final int oldUi = mCurrentUi;
-            mCurrentUi = ui;
-
-            switch (oldUi) {
-                case UI_NONE: {
-                    switch (ui) {
-                        case UI_EDITING_PRINT_JOB: {
-                            doUiSwitch(R.layout.print_job_config_activity_content_editing);
-                            registerPrintButtonClickListener();
-                            if (postSwitchCallback != null) {
-                                postSwitchCallback.run();
-                            }
-                        } break;
-
-                        case UI_GENERATING_PRINT_JOB: {
-                            doUiSwitch(R.layout.print_job_config_activity_content_generating);
-                            registerCancelButtonClickListener();
-                            if (postSwitchCallback != null) {
-                                postSwitchCallback.run();
-                            }
-                        } break;
-                    }
-                } break;
-
-                case UI_EDITING_PRINT_JOB: {
-                    switch (ui) {
-                        case UI_GENERATING_PRINT_JOB: {
-                            animateUiSwitch(R.layout.print_job_config_activity_content_generating,
-                                    new Runnable() {
-                                @Override
-                                public void run() {
-                                    registerCancelButtonClickListener();
-                                    if (postSwitchCallback != null) {
-                                        postSwitchCallback.run();
-                                    }
-                                }
-                            },
-                            new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                                    ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
-                        } break;
-
-                        case UI_ERROR: {
-                            animateUiSwitch(R.layout.print_job_config_activity_content_error,
-                                    new Runnable() {
-                                @Override
-                                public void run() {
-                                    registerOkButtonClickListener();
-                                    if (postSwitchCallback != null) {
-                                        postSwitchCallback.run();
-                                    }
-                                }
-                            },
-                            new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                                    ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
-                        } break;
-                    }
-                } break;
-
-                case UI_GENERATING_PRINT_JOB: {
-                    switch (ui) {
-                        case UI_EDITING_PRINT_JOB: {
-                            animateUiSwitch(R.layout.print_job_config_activity_content_editing,
-                                    new Runnable() {
-                                @Override
-                                public void run() {
-                                    registerPrintButtonClickListener();
-                                    if (postSwitchCallback != null) {
-                                        postSwitchCallback.run();
-                                    }
-                                }
-                            },
-                            new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                                    ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER));
-                        } break;
-
-                        case UI_ERROR: {
-                            animateUiSwitch(R.layout.print_job_config_activity_content_error,
-                                    new Runnable() {
-                                @Override
-                                public void run() {
-                                    registerOkButtonClickListener();
-                                    if (postSwitchCallback != null) {
-                                        postSwitchCallback.run();
-                                    }
-                                }
-                            },
-                            new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                                    ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
-                        } break;
-                    }
-                } break;
-
-                case UI_ERROR: {
-                    switch (ui) {
-                        case UI_EDITING_PRINT_JOB: {
-                            animateUiSwitch(R.layout.print_job_config_activity_content_editing,
-                                    new Runnable() {
-                                @Override
-                                public void run() {
-                                    registerPrintButtonClickListener();
-                                    if (postSwitchCallback != null) {
-                                        postSwitchCallback.run();
-                                    }
-                                }
-                            },
-                            new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                                    ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER));
-                        } break;
-                    }
-                } break;
-            }
-        }
-
-        private void registerAdvancedPrintOptionsButtonClickListener() {
-            Button advancedOptionsButton = (Button) findViewById(R.id.advanced_settings_button);
-            advancedOptionsButton.setOnClickListener(new OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    ComponentName serviceName = mCurrentPrinter.getId().getServiceName();
-                    String activityName = getAdvancedOptionsActivityName(serviceName);
-                    if (TextUtils.isEmpty(activityName)) {
-                        return;
-                    }
-                    Intent intent = new Intent(Intent.ACTION_MAIN);
-                    intent.setComponent(new ComponentName(serviceName.getPackageName(),
-                            activityName));
-
-                    List<ResolveInfo> resolvedActivities = getPackageManager()
-                            .queryIntentActivities(intent, 0);
-                    if (resolvedActivities.isEmpty()) {
-                        return;
-                    }
-                    // The activity is a component name, therefore it is one or none.
-                    if (resolvedActivities.get(0).activityInfo.exported) {
-                        PrintJobInfo printJobInfo = mSpoolerProvider.getSpooler().getPrintJobInfo(
-                                mPrintJobId, PrintManager.APP_ID_ANY);
-                        intent.putExtra(PrintService.EXTRA_PRINT_JOB_INFO, printJobInfo);
-                        // TODO: Make this an API for the next release.
-                        intent.putExtra("android.intent.extra.print.EXTRA_PRINTER_INFO",
-                                mCurrentPrinter);
-                        try {
-                            startActivityForResult(intent,
-                                    ACTIVITY_POPULATE_ADVANCED_PRINT_OPTIONS);
-                        } catch (ActivityNotFoundException anfe) {
-                            Log.e(LOG_TAG, "Error starting activity for intent: " + intent, anfe);
-                        }
-                    }
-                }
-            });
-        }
-
-        private void registerPrintButtonClickListener() {
-            Button printButton = (Button) findViewById(R.id.print_button);
-            printButton.setOnClickListener(new OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
-                    if (printer != null) {
-                        mEditor.confirmPrint();
-                        mController.update();
-                        if (!printer.equals(mDestinationSpinnerAdapter.mFakePdfPrinter)) {
-                            mEditor.refreshCurrentPrinter();
-                        }
-                    } else {
-                        mEditor.cancel();
-                        PrintJobConfigActivity.this.finish();
-                    }
-                }
-            });
-        }
-
-        private void registerCancelButtonClickListener() {
-            Button cancelButton = (Button) findViewById(R.id.cancel_button);
-            cancelButton.setOnClickListener(new OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    if (!mController.isWorking()) {
-                        PrintJobConfigActivity.this.finish();
-                    }
-                    mEditor.cancel();
-                }
-            });
-        }
-
-        private void registerOkButtonClickListener() {
-            Button okButton = (Button) findViewById(R.id.ok_button);
-            okButton.setOnClickListener(new OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    mEditor.showUi(Editor.UI_EDITING_PRINT_JOB, new Runnable() {
-                        @Override
-                        public void run() {
-                            // Start over with a clean slate.
-                            mOldPrintAttributes.clear();
-                            mController.initialize();
-                            mEditor.initialize();
-                            mEditor.bindUi();
-                            mEditor.reselectCurrentPrinter();
-                            if (!mController.hasPerformedLayout()) {
-                                mController.update();
-                            }
-                        }
-                    });
-                }
-            });
-        }
-
-        private void doUiSwitch(int showLayoutId) {
-            ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
-            contentContainer.removeAllViews();
-            getLayoutInflater().inflate(showLayoutId, contentContainer, true);
-        }
-
-        private void animateUiSwitch(int showLayoutId, final Runnable beforeShowNewUiAction,
-                final LayoutParams containerParams) {
-            // Find everything we will shuffle around.
-            final ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
-            final View hidingView = contentContainer.getChildAt(0);
-            final View showingView = getLayoutInflater().inflate(showLayoutId,
-                    null, false);
-
-            // First animation - fade out the old content.
-            AutoCancellingAnimator.animate(hidingView).alpha(0.0f)
-                    .withLayer().withEndAction(new Runnable() {
-                @Override
-                public void run() {
-                    hidingView.setVisibility(View.INVISIBLE);
-
-                    // Prepare the new content with correct size and alpha.
-                    showingView.setMinimumWidth(contentContainer.getWidth());
-                    showingView.setAlpha(0.0f);
-
-                    // Compute how to much shrink /stretch the content.
-                    final int widthSpec = MeasureSpec.makeMeasureSpec(
-                            contentContainer.getWidth(), MeasureSpec.UNSPECIFIED);
-                    final int heightSpec = MeasureSpec.makeMeasureSpec(
-                            contentContainer.getHeight(), MeasureSpec.UNSPECIFIED);
-                    showingView.measure(widthSpec, heightSpec);
-                    final float scaleY = (float) showingView.getMeasuredHeight()
-                            / (float) contentContainer.getHeight();
-
-                    // Second animation - resize the container.
-                    AutoCancellingAnimator.animate(contentContainer).scaleY(scaleY)
-                            .withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            // Swap the old and the new content.
-                            contentContainer.removeAllViews();
-                            contentContainer.setScaleY(1.0f);
-                            contentContainer.addView(showingView);
-
-                            contentContainer.setLayoutParams(containerParams);
-
-                            beforeShowNewUiAction.run();
-
-                            // Third animation - show the new content.
-                            AutoCancellingAnimator.animate(showingView).alpha(1.0f);
-                        }
-                    });
-                }
-            });
-        }
-
-        public void initialize() {
-            mEditorState = EDITOR_STATE_INITIALIZED;
-        }
-
-        public boolean isCancelled() {
-            return mEditorState == EDITOR_STATE_CANCELLED;
-        }
-
-        public void cancel() {
-            mEditorState = EDITOR_STATE_CANCELLED;
-            mController.cancel();
-            updateUi();
-        }
-
-        public boolean isDone() {
-            return isPrintConfirmed() || isCancelled();
-        }
-
-        public boolean isPrintConfirmed() {
-            return mEditorState == EDITOR_STATE_CONFIRMED_PRINT;
-        }
-
-        public void confirmPrint() {
-            addCurrentPrinterToHistory();
-            mEditorState = EDITOR_STATE_CONFIRMED_PRINT;
-            showUi(UI_GENERATING_PRINT_JOB, null);
-        }
-
-        public PageRange[] getRequestedPages() {
-            if (hasErrors()) {
-                return null;
-            }
-            if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
-                List<PageRange> pageRanges = new ArrayList<PageRange>();
-                mStringCommaSplitter.setString(mPageRangeEditText.getText().toString());
-
-                while (mStringCommaSplitter.hasNext()) {
-                    String range = mStringCommaSplitter.next().trim();
-                    if (TextUtils.isEmpty(range)) {
-                        continue;
-                    }
-                    final int dashIndex = range.indexOf('-');
-                    final int fromIndex;
-                    final int toIndex;
-
-                    if (dashIndex > 0) {
-                        fromIndex = Integer.parseInt(range.substring(0, dashIndex).trim()) - 1;
-                        // It is possible that the dash is at the end since the input
-                        // verification can has to allow the user to keep entering if
-                        // this would lead to a valid input. So we handle this.
-                        toIndex = (dashIndex < range.length() - 1)
-                                ? Integer.parseInt(range.substring(dashIndex + 1,
-                                        range.length()).trim()) - 1 : fromIndex;
-                    } else {
-                        fromIndex = toIndex = Integer.parseInt(range) - 1;
-                    }
-
-                    PageRange pageRange = new PageRange(Math.min(fromIndex, toIndex),
-                            Math.max(fromIndex, toIndex));
-                    pageRanges.add(pageRange);
-                }
-
-                PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
-                pageRanges.toArray(pageRangesArray);
-
-                return PageRangeUtils.normalize(pageRangesArray);
-            }
-
-            return ALL_PAGES_ARRAY;
-        }
-
-        private void bindUi() {
-            if (mCurrentUi != UI_EDITING_PRINT_JOB) {
-                return;
-            }
-
-            // Content container
-            mContentContainer = findViewById(R.id.content_container);
-
-            // Copies
-            mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
-            mCopiesEditText.setOnFocusChangeListener(mFocusListener);
-            mCopiesEditText.setText(MIN_COPIES_STRING);
-            mCopiesEditText.setSelection(mCopiesEditText.getText().length());
-            mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
-            if (!TextUtils.equals(mCopiesEditText.getText(), MIN_COPIES_STRING)) {
-                mIgnoreNextCopiesChange = true;
-            }
-            mSpoolerProvider.getSpooler().setPrintJobCopiesNoPersistence(
-                    mPrintJobId, MIN_COPIES);
-
-            // Destination.
-            mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
-            mDestinationSpinner.setDropDownWidth(ViewGroup.LayoutParams.MATCH_PARENT);
-            mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
-            mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-            if (mDestinationSpinnerAdapter.getCount() > 0) {
-                mIgnoreNextDestinationChange = true;
-            }
-
-            // Media size.
-            mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner);
-            mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
-            mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-            if (mMediaSizeSpinnerAdapter.getCount() > 0) {
-                mOldMediaSizeSelectionIndex = 0;
-            }
-
-            // Color mode.
-            mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner);
-            mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
-            mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-            if (mColorModeSpinnerAdapter.getCount() > 0) {
-                mOldColorModeSelectionIndex = 0;
-            }
-
-            // Orientation
-            mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
-            mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
-            mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-            if (mOrientationSpinnerAdapter.getCount() > 0) {
-                mIgnoreNextOrientationChange = true;
-            }
-
-            // Range options
-            mRangeOptionsTitle = (TextView) findViewById(R.id.range_options_title);
-            mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner);
-            mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter);
-            mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-            if (mRangeOptionsSpinnerAdapter.getCount() > 0) {
-                mIgnoreNextRangeOptionChange = true;
-            }
-
-            // Page range
-            mPageRangeTitle = (TextView) findViewById(R.id.page_range_title);
-            mPageRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
-            mPageRangeEditText.setOnFocusChangeListener(mFocusListener);
-            mPageRangeEditText.addTextChangedListener(mRangeTextWatcher);
-
-            // Advanced options button.
-            mAdvancedPrintOptionsContainer = findViewById(R.id.advanced_settings_container);
-            mAdvancedOptionsButton = (Button) findViewById(R.id.advanced_settings_button);
-            registerAdvancedPrintOptionsButtonClickListener();
-
-            // Print button
-            mPrintButton = (Button) findViewById(R.id.print_button);
-            registerPrintButtonClickListener();
-        }
-
-        public boolean updateUi() {
-            if (mCurrentUi != UI_EDITING_PRINT_JOB) {
-                return false;
-            }
-            if (isPrintConfirmed() || isCancelled()) {
-                mDestinationSpinner.setEnabled(false);
-                mCopiesEditText.setEnabled(false);
-                mMediaSizeSpinner.setEnabled(false);
-                mColorModeSpinner.setEnabled(false);
-                mOrientationSpinner.setEnabled(false);
-                mRangeOptionsSpinner.setEnabled(false);
-                mPageRangeEditText.setEnabled(false);
-                mPrintButton.setEnabled(false);
-                mAdvancedOptionsButton.setEnabled(false);
-                return false;
-            }
-
-            // If a printer with capabilities is selected, then we enabled all options.
-            boolean allOptionsEnabled = false;
-            final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
-            if (selectedIndex >= 0) {
-                Object item = mDestinationSpinnerAdapter.getItem(selectedIndex);
-                if (item instanceof PrinterInfo) {
-                    PrinterInfo printer = (PrinterInfo) item;
-                    if (printer.getCapabilities() != null
-                            && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) {
-                        allOptionsEnabled = true;
-                    }
-                }
-            }
-
-            if (!allOptionsEnabled) {
-                mCopiesEditText.setEnabled(false);
-                mMediaSizeSpinner.setEnabled(false);
-                mColorModeSpinner.setEnabled(false);
-                mOrientationSpinner.setEnabled(false);
-                mRangeOptionsSpinner.setEnabled(false);
-                mPageRangeEditText.setEnabled(false);
-                mPrintButton.setEnabled(false);
-                mAdvancedOptionsButton.setEnabled(false);
-                return false;
-            } else {
-                boolean someAttributeSelectionChanged = false;
-
-                PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
-                PrinterCapabilitiesInfo capabilities = printer.getCapabilities();
-                PrintAttributes defaultAttributes = printer.getCapabilities().getDefaults();
-
-                // Media size.
-                // Sort the media sizes based on the current locale.
-                List<MediaSize> mediaSizes = new ArrayList<MediaSize>(capabilities.getMediaSizes());
-                Collections.sort(mediaSizes, mMediaSizeComparator);
-
-                // If the media sizes changed, we update the adapter and the spinner.
-                boolean mediaSizesChanged = false;
-                final int mediaSizeCount = mediaSizes.size();
-                if (mediaSizeCount != mMediaSizeSpinnerAdapter.getCount()) {
-                    mediaSizesChanged = true;
-                } else {
-                    for (int i = 0; i < mediaSizeCount; i++) {
-                        if (!mediaSizes.get(i).equals(mMediaSizeSpinnerAdapter.getItem(i).value)) {
-                            mediaSizesChanged = true;
-                            break;
-                        }
-                    }
-                }
-                if (mediaSizesChanged) {
-                    // Remember the old media size to try selecting it again.
-                    int oldMediaSizeNewIndex = AdapterView.INVALID_POSITION;
-                    MediaSize oldMediaSize = mCurrPrintAttributes.getMediaSize();
-
-                    // Rebuild the adapter data.
-                    mMediaSizeSpinnerAdapter.clear();
-                    for (int i = 0; i < mediaSizeCount; i++) {
-                        MediaSize mediaSize = mediaSizes.get(i);
-                        if (mediaSize.asPortrait().equals(oldMediaSize.asPortrait())) {
-                            // Update the index of the old selection.
-                            oldMediaSizeNewIndex = i;
-                        }
-                        mMediaSizeSpinnerAdapter.add(new SpinnerItem<MediaSize>(
-                                mediaSize, mediaSize.getLabel(getPackageManager())));
-                    }
-
-                    mMediaSizeSpinner.setEnabled(true);
-
-                    if (oldMediaSizeNewIndex != AdapterView.INVALID_POSITION) {
-                        // Select the old media size - nothing really changed.
-                        setMediaSizeSpinnerSelectionNoCallback(oldMediaSizeNewIndex);
-                    } else {
-                        // Select the first or the default and mark if selection changed.
-                        final int mediaSizeIndex = Math.max(mediaSizes.indexOf(
-                                defaultAttributes.getMediaSize()), 0);
-                        setMediaSizeSpinnerSelectionNoCallback(mediaSizeIndex);
-                        if (oldMediaSize.isPortrait()) {
-                            mCurrPrintAttributes.setMediaSize(mMediaSizeSpinnerAdapter
-                                    .getItem(mediaSizeIndex).value.asPortrait());
-                        } else {
-                            mCurrPrintAttributes.setMediaSize(mMediaSizeSpinnerAdapter
-                                    .getItem(mediaSizeIndex).value.asLandscape());
-                        }
-                        someAttributeSelectionChanged = true;
-                    }
-                }
-                mMediaSizeSpinner.setEnabled(true);
-
-                // Color mode.
-                final int colorModes = capabilities.getColorModes();
-
-                // If the color modes changed, we update the adapter and the spinner.
-                boolean colorModesChanged = false;
-                if (Integer.bitCount(colorModes) != mColorModeSpinnerAdapter.getCount()) {
-                    colorModesChanged = true;
-                } else {
-                    int remainingColorModes = colorModes;
-                    int adapterIndex = 0;
-                    while (remainingColorModes != 0) {
-                        final int colorBitOffset = Integer.numberOfTrailingZeros(
-                                remainingColorModes);
-                        final int colorMode = 1 << colorBitOffset;
-                        remainingColorModes &= ~colorMode;
-                        if (colorMode != mColorModeSpinnerAdapter.getItem(adapterIndex).value) {
-                            colorModesChanged = true;
-                            break;
-                        }
-                        adapterIndex++;
-                    }
-                }
-                if (colorModesChanged) {
-                    // Remember the old color mode to try selecting it again.
-                    int oldColorModeNewIndex = AdapterView.INVALID_POSITION;
-                    final int oldColorMode = mCurrPrintAttributes.getColorMode();
-
-                    // Rebuild the adapter data.
-                    mColorModeSpinnerAdapter.clear();
-                    String[] colorModeLabels = getResources().getStringArray(
-                            R.array.color_mode_labels);
-                    int remainingColorModes = colorModes;
-                    while (remainingColorModes != 0) {
-                        final int colorBitOffset = Integer.numberOfTrailingZeros(
-                                remainingColorModes);
-                        final int colorMode = 1 << colorBitOffset;
-                        if (colorMode == oldColorMode) {
-                            // Update the index of the old selection.
-                            oldColorModeNewIndex = colorBitOffset;
-                        }
-                        remainingColorModes &= ~colorMode;
-                        mColorModeSpinnerAdapter.add(new SpinnerItem<Integer>(colorMode,
-                                colorModeLabels[colorBitOffset]));
-                    }
-                    mColorModeSpinner.setEnabled(true);
-                    if (oldColorModeNewIndex != AdapterView.INVALID_POSITION) {
-                        // Select the old color mode - nothing really changed.
-                        setColorModeSpinnerSelectionNoCallback(oldColorModeNewIndex);
-                    } else {
-                        final int selectedColorMode = colorModes & defaultAttributes.getColorMode();
-                        final int itemCount = mColorModeSpinnerAdapter.getCount();
-                        for (int i = 0; i < itemCount; i++) {
-                            SpinnerItem<Integer> item = mColorModeSpinnerAdapter.getItem(i);
-                            if (selectedColorMode == item.value) {
-                                setColorModeSpinnerSelectionNoCallback(i);
-                                mCurrPrintAttributes.setColorMode(selectedColorMode);
-                                someAttributeSelectionChanged = true;
-                            }
-                        }
-                    }
-                }
-                mColorModeSpinner.setEnabled(true);
-
-                // Orientation
-                MediaSize mediaSize = mCurrPrintAttributes.getMediaSize();
-                if (mediaSize.isPortrait()
-                        && mOrientationSpinner.getSelectedItemPosition() != 0) {
-                    mIgnoreNextOrientationChange = true;
-                    mOrientationSpinner.setSelection(0);
-                } else if (!mediaSize.isPortrait()
-                        && mOrientationSpinner.getSelectedItemPosition() != 1) {
-                    mIgnoreNextOrientationChange = true;
-                    mOrientationSpinner.setSelection(1);
-                }
-                mOrientationSpinner.setEnabled(true);
-
-                // Range options
-                PrintDocumentInfo info = mDocument.info;
-                if (info != null && info.getPageCount() > 0) {
-                    if (info.getPageCount() == 1) {
-                        mRangeOptionsSpinner.setEnabled(false);
-                    } else {
-                        mRangeOptionsSpinner.setEnabled(true);
-                        if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
-                            if (!mPageRangeEditText.isEnabled()) {
-                                mPageRangeEditText.setEnabled(true);
-                                mPageRangeEditText.setVisibility(View.VISIBLE);
-                                mPageRangeTitle.setVisibility(View.VISIBLE);
-                                mPageRangeEditText.requestFocus();
-                                InputMethodManager imm = (InputMethodManager)
-                                        getSystemService(INPUT_METHOD_SERVICE);
-                                imm.showSoftInput(mPageRangeEditText, 0);
-                            }
-                        } else {
-                            mPageRangeEditText.setEnabled(false);
-                            mPageRangeEditText.setVisibility(View.INVISIBLE);
-                            mPageRangeTitle.setVisibility(View.INVISIBLE);
-                        }
-                    }
-                    final int pageCount = mDocument.info.getPageCount();
-                    String title = (pageCount != PrintDocumentInfo.PAGE_COUNT_UNKNOWN)
-                            ? getString(R.string.label_pages, String.valueOf(pageCount))
-                            : getString(R.string.page_count_unknown);
-                    mRangeOptionsTitle.setText(title);
-                } else {
-                    if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
-                        mIgnoreNextRangeOptionChange = true;
-                        mRangeOptionsSpinner.setSelection(0);
-                    }
-                    mRangeOptionsSpinner.setEnabled(false);
-                    mRangeOptionsTitle.setText(getString(R.string.page_count_unknown));
-                    mPageRangeEditText.setEnabled(false);
-                    mPageRangeEditText.setVisibility(View.INVISIBLE);
-                    mPageRangeTitle.setVisibility(View.INVISIBLE);
-                }
-
-                // Advanced print options
-                ComponentName serviceName = mCurrentPrinter.getId().getServiceName();
-                if (!TextUtils.isEmpty(getAdvancedOptionsActivityName(serviceName))) {
-                    mAdvancedPrintOptionsContainer.setVisibility(View.VISIBLE);
-                    mAdvancedOptionsButton.setEnabled(true);
-                } else {
-                    mAdvancedPrintOptionsContainer.setVisibility(View.GONE);
-                    mAdvancedOptionsButton.setEnabled(false);
-                }
-
-                // Print
-                if (mDestinationSpinner.getSelectedItemId()
-                        != DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF) {
-                    String newText = getString(R.string.print_button);
-                    if (!TextUtils.equals(newText, mPrintButton.getText())) {
-                        mPrintButton.setText(R.string.print_button);
-                    }
-                } else {
-                    String newText = getString(R.string.save_button);
-                    if (!TextUtils.equals(newText, mPrintButton.getText())) {
-                        mPrintButton.setText(R.string.save_button);
-                    }
-                }
-                if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1
-                            && (TextUtils.isEmpty(mPageRangeEditText.getText()) || hasErrors()))
-                        || (mRangeOptionsSpinner.getSelectedItemPosition() == 0
-                            && (!mController.hasPerformedLayout() || hasErrors()))) {
-                    mPrintButton.setEnabled(false);
-                } else {
-                    mPrintButton.setEnabled(true);
-                }
-
-                // Copies
-                if (mDestinationSpinner.getSelectedItemId()
-                        != DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF) {
-                    mCopiesEditText.setEnabled(true);
-                } else {
-                    mCopiesEditText.setEnabled(false);
-                }
-                if (mCopiesEditText.getError() == null
-                        && TextUtils.isEmpty(mCopiesEditText.getText())) {
-                    mIgnoreNextCopiesChange = true;
-                    mCopiesEditText.setText(String.valueOf(MIN_COPIES));
-                    mCopiesEditText.requestFocus();
-                }
-
-                return someAttributeSelectionChanged;
-            }
-        }
-
-        private String getAdvancedOptionsActivityName(ComponentName serviceName) {
-            PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
-            List<PrintServiceInfo> printServices = printManager.getEnabledPrintServices();
-            final int printServiceCount = printServices.size();
-            for (int i = 0; i < printServiceCount; i ++) {
-                PrintServiceInfo printServiceInfo = printServices.get(i);
-                ServiceInfo serviceInfo = printServiceInfo.getResolveInfo().serviceInfo;
-                if (serviceInfo.name.equals(serviceName.getClassName())
-                        && serviceInfo.packageName.equals(serviceName.getPackageName())) {
-                    return printServiceInfo.getAdvancedOptionsActivityName();
-                }
-            }
-            return null;
-        }
-
-        private void setMediaSizeSpinnerSelectionNoCallback(int position) {
-            if (mMediaSizeSpinner.getSelectedItemPosition() != position) {
-                mOldMediaSizeSelectionIndex = position;
-                mMediaSizeSpinner.setSelection(position);
-            }
-        }
-
-        private void setColorModeSpinnerSelectionNoCallback(int position) {
-            if (mColorModeSpinner.getSelectedItemPosition() != position) {
-                mOldColorModeSelectionIndex = position;
-                mColorModeSpinner.setSelection(position);
-            }
-        }
-
-        private void startSelectPrinterActivity() {
-            Intent intent = new Intent(PrintJobConfigActivity.this,
-                    SelectPrinterActivity.class);
-            startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER);
-        }
-
-        private boolean hasErrors() {
-            if (mCopiesEditText.getError() != null) {
-                return true;
-            }
-            return mPageRangeEditText.getVisibility() == View.VISIBLE
-                    && mPageRangeEditText.getError() != null;
-        }
-
-        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 WaitForPrinterCapabilitiesTimeout implements Runnable {
-            private static final long GET_CAPABILITIES_TIMEOUT_MILLIS = 10000; // 10sec
-
-            private boolean mIsPosted;
-
-            public void post() {
-                if (!mIsPosted) {
-                    mDestinationSpinner.postDelayed(this,
-                            GET_CAPABILITIES_TIMEOUT_MILLIS);
-                    mIsPosted = true;
-                }
-            }
-
-            public void remove() {
-                if (mIsPosted) {
-                    mIsPosted = false;
-                    mDestinationSpinner.removeCallbacks(this);
-                }
-            }
-
-            public boolean isPosted() {
-                return mIsPosted;
-            }
-
-            @Override
-            public void run() {
-                mIsPosted = false;
-                if (mDestinationSpinner.getSelectedItemPosition() >= 0) {
-                    View itemView = mDestinationSpinner.getSelectedView();
-                    TextView titleView = (TextView) itemView.findViewById(R.id.subtitle);
-                    try {
-                        PackageInfo packageInfo = getPackageManager().getPackageInfo(
-                                mCurrentPrinter.getId().getServiceName().getPackageName(), 0);
-                        CharSequence service = packageInfo.applicationInfo.loadLabel(
-                                getPackageManager());
-                        String subtitle = getString(R.string.printer_unavailable, service.toString());
-                        titleView.setText(subtitle);
-                    } catch (NameNotFoundException nnfe) {
-                        /* ignore */
-                    }
-                }
-            }
-        }
-
-        private final class DestinationAdapter extends BaseAdapter
-                implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>{
-            private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
-
-            private PrinterInfo mFakePdfPrinter;
-
-            public DestinationAdapter() {
-                getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
-            }
-
-            public int getPrinterIndex(PrinterId printerId) {
-                for (int i = 0; i < getCount(); i++) {
-                    PrinterInfo printer = (PrinterInfo) getItem(i);
-                    if (printer != null && printer.getId().equals(printerId)) {
-                        return i;
-                    }
-                }
-                return AdapterView.INVALID_POSITION;
-            }
-
-            public void ensurePrinterInVisibleAdapterPosition(PrinterId printerId) {
-                final int printerCount = mPrinters.size();
-                for (int i = 0; i < printerCount; i++) {
-                    PrinterInfo printer = (PrinterInfo) mPrinters.get(i);
-                    if (printer.getId().equals(printerId)) {
-                        // If already in the list - do nothing.
-                        if (i < getCount() - 2) {
-                            return;
-                        }
-                        // Else replace the last one (two items are not printers).
-                        final int lastPrinterIndex = getCount() - 3;
-                        mPrinters.set(i, mPrinters.get(lastPrinterIndex));
-                        mPrinters.set(lastPrinterIndex, printer);
-                        notifyDataSetChanged();
-                        return;
-                    }
-                }
-            }
-
-            @Override
-            public int getCount() {
-                if (mFakePdfPrinter == null) {
-                    return 0;
-                }
-                return Math.min(mPrinters.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT);
-            }
-
-            @Override
-            public boolean isEnabled(int position) {
-                Object item = getItem(position);
-                if (item instanceof PrinterInfo) {
-                    PrinterInfo printer = (PrinterInfo) item;
-                    return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
-                }
-                return true;
-            }
-
-            @Override
-            public Object getItem(int position) {
-                if (mPrinters.isEmpty()) {
-                    if (position == 0 && mFakePdfPrinter != null) {
-                        return mFakePdfPrinter;
-                    }
-                } else {
-                    if (position < 1) {
-                        return mPrinters.get(position);
-                    }
-                    if (position == 1 && mFakePdfPrinter != null) {
-                        return mFakePdfPrinter;
-                    }
-                    if (position < getCount() - 1) {
-                        return mPrinters.get(position - 1);
-                    }
-                }
-                return null;
-            }
-
-            @Override
-            public long getItemId(int position) {
-                if (mPrinters.isEmpty()) {
-                    if (mFakePdfPrinter != null) {
-                        if (position == 0) {
-                            return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
-                        } else if (position == 1) {
-                            return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
-                        }
-                    }
-                } else {
-                    if (position == 1 && mFakePdfPrinter != null) {
-                        return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
-                    }
-                    if (position == getCount() - 1) {
-                        return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
-                    }
-                }
-                return position;
-            }
-
-            @Override
-            public View getDropDownView(int position, View convertView,
-                    ViewGroup parent) {
-                View view = getView(position, convertView, parent);
-                view.setEnabled(isEnabled(position));
-                return view;
-            }
-
-            @Override
-            public View getView(int position, View convertView, ViewGroup parent) {
-                if (convertView == null) {
-                    convertView = getLayoutInflater().inflate(
-                            R.layout.printer_dropdown_item, parent, false);
-                }
-
-                CharSequence title = null;
-                CharSequence subtitle = null;
-                Drawable icon = null;
-
-                if (mPrinters.isEmpty()) {
-                    if (position == 0 && mFakePdfPrinter != null) {
-                        PrinterInfo printer = (PrinterInfo) getItem(position);
-                        title = printer.getName();
-                    } else if (position == 1) {
-                        title = getString(R.string.all_printers);
-                    }
-                } else {
-                    if (position == 1 && mFakePdfPrinter != null) {
-                        PrinterInfo printer = (PrinterInfo) getItem(position);
-                        title = printer.getName();
-                    } else if (position == getCount() - 1) {
-                        title = getString(R.string.all_printers);
-                    } else {
-                        PrinterInfo printer = (PrinterInfo) getItem(position);
-                        title = printer.getName();
-                        try {
-                            PackageInfo packageInfo = getPackageManager().getPackageInfo(
-                                    printer.getId().getServiceName().getPackageName(), 0);
-                            subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager());
-                            icon = packageInfo.applicationInfo.loadIcon(getPackageManager());
-                        } catch (NameNotFoundException nnfe) {
-                            /* ignore */
-                        }
-                    }
-                }
-
-                TextView titleView = (TextView) convertView.findViewById(R.id.title);
-                titleView.setText(title);
-
-                TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
-                if (!TextUtils.isEmpty(subtitle)) {
-                    subtitleView.setText(subtitle);
-                    subtitleView.setVisibility(View.VISIBLE);
-                } else {
-                    subtitleView.setText(null);
-                    subtitleView.setVisibility(View.GONE);
-                }
-
-                ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
-                if (icon != null) {
-                    iconView.setImageDrawable(icon);
-                    iconView.setVisibility(View.VISIBLE);
-                } else {
-                    iconView.setVisibility(View.INVISIBLE);
-                }
-
-                return convertView;
-            }
-
-            @Override
-            public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
-                if (id == LOADER_ID_PRINTERS_LOADER) {
-                    return new FusedPrintersProvider(PrintJobConfigActivity.this);
-                }
-                return null;
-            }
-
-            @Override
-            public void onLoadFinished(Loader<List<PrinterInfo>> loader,
-                    List<PrinterInfo> printers) {
-                // If this is the first load, create the fake PDF printer.
-                // We do this to avoid flicker where the PDF printer is the
-                // only one and as soon as the loader loads the favorites
-                // it gets switched. Not a great user experience.
-                if (mFakePdfPrinter == null) {
-                    mCurrentPrinter = mFakePdfPrinter = createFakePdfPrinter();
-                    updatePrintAttributes(mCurrentPrinter.getCapabilities());
-                    updateUi();
-                }
-
-                // We rearrange the printers if the user selects a printer
-                // not shown in the initial short list. Therefore, we have
-                // to keep the printer order.
-
-                // No old printers - do not bother keeping their position.
-                if (mPrinters.isEmpty()) {
-                    mPrinters.addAll(printers);
-                    mEditor.ensureCurrentPrinterSelected();
-                    notifyDataSetChanged();
-                    return;
-                }
-
-                // Add the new printers to a map.
-                ArrayMap<PrinterId, PrinterInfo> newPrintersMap =
-                        new ArrayMap<PrinterId, PrinterInfo>();
-                final int printerCount = printers.size();
-                for (int i = 0; i < printerCount; i++) {
-                    PrinterInfo printer = printers.get(i);
-                    newPrintersMap.put(printer.getId(), printer);
-                }
-
-                List<PrinterInfo> newPrinters = new ArrayList<PrinterInfo>();
-
-                // Update printers we already have.
-                final int oldPrinterCount = mPrinters.size();
-                for (int i = 0; i < oldPrinterCount; i++) {
-                    PrinterId oldPrinterId = mPrinters.get(i).getId();
-                    PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId);
-                    if (updatedPrinter != null) {
-                        newPrinters.add(updatedPrinter);
-                    }
-                }
-
-                // Add the rest of the new printers, i.e. what is left.
-                newPrinters.addAll(newPrintersMap.values());
-
-                mPrinters.clear();
-                mPrinters.addAll(newPrinters);
-
-                mEditor.ensureCurrentPrinterSelected();
-                notifyDataSetChanged();
-            }
-
-            @Override
-            public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
-                mPrinters.clear();
-                notifyDataSetInvalidated();
-            }
-
-
-            private PrinterInfo createFakePdfPrinter() {
-                MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintJobConfigActivity.this);
-
-                PrinterId printerId = new PrinterId(getComponentName(), "PDF printer");
-
-                PrinterCapabilitiesInfo.Builder builder =
-                        new PrinterCapabilitiesInfo.Builder(printerId);
-
-                String[] mediaSizeIds = getResources().getStringArray(
-                        R.array.pdf_printer_media_sizes);
-                final int mediaSizeIdCount = mediaSizeIds.length;
-                for (int i = 0; i < mediaSizeIdCount; i++) {
-                    String id = mediaSizeIds[i];
-                    MediaSize mediaSize = MediaSize.getStandardMediaSizeById(id);
-                    builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize));
-                }
-
-                builder.addResolution(new Resolution("PDF resolution", "PDF resolution",
-                            300, 300), true);
-                builder.setColorModes(PrintAttributes.COLOR_MODE_COLOR
-                        | PrintAttributes.COLOR_MODE_MONOCHROME,
-                        PrintAttributes.COLOR_MODE_COLOR);
-
-                return new PrinterInfo.Builder(printerId, getString(R.string.save_as_pdf),
-                        PrinterInfo.STATUS_IDLE)
-                    .setCapabilities(builder.build())
-                    .build();
-            }
-        }
-    }
-
-    /**
-     * 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[] ourRanges, PageRange[] otherRanges) {
-            if (ourRanges == null || otherRanges == null) {
-                return false;
-            }
-
-            if (ourRanges.length == 1
-                    && PageRange.ALL_PAGES.equals(ourRanges[0])) {
-                return true;
-            }
-
-            ourRanges = normalize(ourRanges);
-            otherRanges = normalize(otherRanges);
-
-            // Note that the code below relies on the ranges being normalized
-            // which is they contain monotonically increasing non-intersecting
-            // subranges whose start is less that or equal to the end.
-            int otherRangeIdx = 0;
-            final int ourRangeCount = ourRanges.length;
-            final int otherRangeCount = otherRanges.length;
-            for (int ourRangeIdx = 0; ourRangeIdx < ourRangeCount; ourRangeIdx++) {
-                PageRange ourRange = ourRanges[ourRangeIdx];
-                for (; otherRangeIdx < otherRangeCount; otherRangeIdx++) {
-                    PageRange otherRange = otherRanges[otherRangeIdx];
-                    if (otherRange.getStart() > ourRange.getEnd()) {
-                        break;
-                    }
-                    if (otherRange.getStart() < ourRange.getStart()
-                            || otherRange.getEnd() > ourRange.getEnd()) {
-                        return false;
-                    }
-                }
-            }
-            if (otherRangeIdx < otherRangeCount) {
-                return false;
-            }
-            return true;
-        }
-
-        public static PageRange[] normalize(PageRange[] pageRanges) {
-            if (pageRanges == null) {
-                return null;
-            }
-            final int oldRangeCount = pageRanges.length;
-            if (oldRangeCount <= 1) {
-                return pageRanges;
-            }
-            Arrays.sort(pageRanges, sComparator);
-            int newRangeCount = 1;
-            for (int i = 0; i < oldRangeCount - 1; i++) {
-                newRangeCount++;
-                PageRange currentRange = pageRanges[i];
-                PageRange nextRange = pageRanges[i + 1];
-                if (currentRange.getEnd() + 1 >= nextRange.getStart()) {
-                    newRangeCount--;
-                    pageRanges[i] = null;
-                    pageRanges[i + 1] = new PageRange(currentRange.getStart(),
-                            Math.max(currentRange.getEnd(), nextRange.getEnd()));
-                }
-            }
-            if (newRangeCount == oldRangeCount) {
-                return pageRanges;
-            }
-            return Arrays.copyOfRange(pageRanges, oldRangeCount - newRangeCount,
-                    oldRangeCount);
-        }
-
-        public static void offset(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);
-            }
-        }
-    }
-
-    private static final class AutoCancellingAnimator
-            implements OnAttachStateChangeListener, Runnable {
-
-        private ViewPropertyAnimator mAnimator;
-
-        private boolean mCancelled;
-        private Runnable mEndCallback;
-
-        public static AutoCancellingAnimator animate(View view) {
-            ViewPropertyAnimator animator = view.animate();
-            AutoCancellingAnimator cancellingWrapper =
-                    new AutoCancellingAnimator(animator);
-            view.addOnAttachStateChangeListener(cancellingWrapper);
-            return cancellingWrapper;
-        }
-
-        private AutoCancellingAnimator(ViewPropertyAnimator animator) {
-            mAnimator = animator;
-        }
-
-        public AutoCancellingAnimator alpha(float alpha) {
-            mAnimator = mAnimator.alpha(alpha);
-            return this;
-        }
-
-        public void cancel() {
-            mAnimator.cancel();
-        }
-
-        public AutoCancellingAnimator withLayer() {
-            mAnimator = mAnimator.withLayer();
-            return this;
-        }
-
-        public AutoCancellingAnimator withEndAction(Runnable callback) {
-            mEndCallback = callback;
-            mAnimator = mAnimator.withEndAction(this);
-            return this;
-        }
-
-        public AutoCancellingAnimator scaleY(float scale) {
-            mAnimator = mAnimator.scaleY(scale);
-            return this;
-        }
-
-        @Override
-        public void onViewAttachedToWindow(View v) {
-            /* do nothing */
-        }
-
-        @Override
-        public void onViewDetachedFromWindow(View v) {
-            cancel();
-        }
-
-        @Override
-        public void run() {
-            if (!mCancelled) {
-                mEndCallback.run();
-            }
-        }
-    }
-
-    private static final class PrintSpoolerProvider implements ServiceConnection {
-        private final Context mContext;
-        private final Runnable mCallback;
-
-        private PrintSpoolerService mSpooler;
-
-        public PrintSpoolerProvider(Context context, Runnable callback) {
-            mContext = context;
-            mCallback = callback;
-            Intent intent = new Intent(mContext, PrintSpoolerService.class);
-            mContext.bindService(intent, this, 0);
-        }
-
-        public PrintSpoolerService getSpooler() {
-            return mSpooler;
-        }
-
-        public void destroy() {
-            if (mSpooler != null) {
-                mContext.unbindService(this);
-            }
-        }
-
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            mSpooler = ((PrintSpoolerService.PrintSpooler) service).getService();
-            if (mSpooler != null) {
-                mCallback.run();
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            /* do noting - we are in the same process */
-        }
-    }
-
-    private static final class PrintDocumentAdapterObserver
-            extends IPrintDocumentAdapterObserver.Stub {
-        private final WeakReference<PrintJobConfigActivity> mWeakActvity;
-
-        public PrintDocumentAdapterObserver(PrintJobConfigActivity activity) {
-            mWeakActvity = new WeakReference<PrintJobConfigActivity>(activity);
-        }
-
-        @Override
-        public void onDestroy() {
-            final PrintJobConfigActivity activity = mWeakActvity.get();
-            if (activity != null) {
-                activity.mController.mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (activity.mController != null) {
-                            activity.mController.cancel();
-                        }
-                        if (activity.mEditor != null) {
-                            activity.mEditor.cancel();
-                        }
-                        activity.finish();
-                    }
-                });
-            }
-        }
-    }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java
deleted file mode 100644
index d9ccb5d..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java
+++ /dev/null
@@ -1,151 +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.printspooler;
-
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.print.ILayoutResultCallback;
-import android.print.IPrintDocumentAdapter;
-import android.print.IWriteResultCallback;
-import android.print.PageRange;
-import android.print.PrintAttributes;
-import android.util.Log;
-
-import libcore.io.IoUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * This class represents a remote print document adapter instance.
- */
-final class RemotePrintDocumentAdapter {
-    private static final String LOG_TAG = "RemotePrintDocumentAdapter";
-
-    private static final boolean DEBUG = false;
-
-    private final IPrintDocumentAdapter mRemoteInterface;
-
-    private final File mFile;
-
-    public RemotePrintDocumentAdapter(IPrintDocumentAdapter printAdatper, File file) {
-        mRemoteInterface = printAdatper;
-        mFile = file;
-    }
-
-    public void start()  {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "start()");
-        }
-        try {
-            mRemoteInterface.start();
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error calling start()", re);
-        }
-    }
-
-    public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
-            ILayoutResultCallback callback, Bundle metadata, int sequence) {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "layout()");
-        }
-        try {
-            mRemoteInterface.layout(oldAttributes, newAttributes, callback, metadata, sequence);
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error calling layout()", re);
-        }
-    }
-
-    public void write(final PageRange[] pages, final IWriteResultCallback callback,
-            final int sequence) {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "write()");
-        }
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                InputStream in = null;
-                OutputStream out = null;
-                ParcelFileDescriptor source = null;
-                ParcelFileDescriptor sink = null;
-                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(pages, sink, callback, sequence);
-
-                    // Close the source. It is now held by the client.
-                    sink.close();
-                    sink = null;
-
-                    // Read the data.
-                    final byte[] buffer = new byte[8192];
-                    while (true) {
-                        final int readByteCount = in.read(buffer);
-                        if (readByteCount < 0) {
-                            break;
-                        }
-                        out.write(buffer, 0, readByteCount);
-                    }
-                } catch (RemoteException re) {
-                    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;
-            }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
-    }
-
-    public void finish() {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "finish()");
-        }
-        try {
-            mRemoteInterface.finish();
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error calling finish()", re);
-        }
-    }
-
-    public void cancel() {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "cancel()");
-        }
-        try {
-            mRemoteInterface.cancel();
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error calling cancel()", re);
-        }
-    }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java
deleted file mode 100644
index 141dbd1..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java
+++ /dev/null
@@ -1,41 +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.printspooler;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.print.PrinterId;
-
-import com.android.printspooler.SelectPrinterFragment.OnPrinterSelectedListener;
-
-public class SelectPrinterActivity extends Activity implements OnPrinterSelectedListener {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.select_printer_activity);
-    }
-
-    @Override
-    public void onPrinterSelected(PrinterId printer) {
-        Intent intent = new Intent();
-        intent.putExtra(PrintJobConfigActivity.INTENT_EXTRA_PRINTER_ID, printer);
-        setResult(RESULT_OK, intent);
-        finish();
-    }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
similarity index 99%
rename from packages/PrintSpooler/src/com/android/printspooler/NotificationController.java
rename to packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index 968a8bf..929f0fc 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.printspooler;
+package com.android.printspooler.model;
 
 import android.app.Notification;
 import android.app.Notification.InboxStyle;
@@ -38,6 +38,8 @@
 import android.provider.Settings;
 import android.util.Log;
 
+import com.android.printspooler.R;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -45,7 +47,7 @@
  * This class is responsible for updating the print notifications
  * based on print job state transitions.
  */
-public class NotificationController {
+final class NotificationController {
     public static final boolean DEBUG = false;
 
     public static final String LOG_TAG = "NotificationController";
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerProvider.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerProvider.java
new file mode 100644
index 0000000..06723c3
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerProvider.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.printspooler.model;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+public class PrintSpoolerProvider implements ServiceConnection {
+    private final Context mContext;
+    private final Runnable mCallback;
+
+    private PrintSpoolerService mSpooler;
+
+    public PrintSpoolerProvider(Context context, Runnable callback) {
+        mContext = context;
+        mCallback = callback;
+        Intent intent = new Intent(mContext, PrintSpoolerService.class);
+        mContext.bindService(intent, this, 0);
+    }
+
+    public PrintSpoolerService getSpooler() {
+        return mSpooler;
+    }
+
+    public void destroy() {
+        if (mSpooler != null) {
+            mContext.unbindService(this);
+        }
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        mSpooler = ((PrintSpoolerService.PrintSpooler) service).getService();
+        if (mSpooler != null) {
+            mCallback.run();
+        }
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        /* do nothing - we are in the same process */
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
similarity index 95%
rename from packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
rename to packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
index 615d667..045a2f9 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.printspooler;
+package com.android.printspooler.model;
 
 import android.app.Service;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -38,7 +39,6 @@
 import android.print.PrintJobInfo;
 import android.print.PrintManager;
 import android.print.PrinterId;
-import android.print.PrinterInfo;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
@@ -48,6 +48,7 @@
 
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.printspooler.R;
 
 import libcore.io.IoUtils;
 
@@ -89,7 +90,7 @@
 
     private final Object mLock = new Object();
 
-    private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
+    private final List<PrintJobInfo> mPrintJobs = new ArrayList<>();
 
     private static PrintSpoolerService sInstance;
 
@@ -274,7 +275,7 @@
                             && isScheduledState(printJob.getState()));
                 if (sameComponent && sameAppId && sameState) {
                     if (foundPrintJobs == null) {
-                        foundPrintJobs = new ArrayList<PrintJobInfo>();
+                        foundPrintJobs = new ArrayList<>();
                     }
                     foundPrintJobs.add(printJob);
                 }
@@ -400,7 +401,7 @@
                 FileOutputStream out = null;
                 try {
                     if (printJob != null) {
-                        File file = generateFileForPrintJob(printJobId);
+                        File file = generateFileForPrintJob(PrintSpoolerService.this, printJobId);
                         in = new FileInputStream(file);
                         out = new FileOutputStream(fd.getFileDescriptor());
                     }
@@ -427,8 +428,8 @@
         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
     }
 
-    public File generateFileForPrintJob(PrintJobId printJobId) {
-        return new File(getFilesDir(), PRINT_JOB_FILE_PREFIX
+    public static File generateFileForPrintJob(Context context, PrintJobId printJobId) {
+        return new File(context.getFilesDir(), PRINT_JOB_FILE_PREFIX
                 + printJobId.flattenToString() + "." + PRINT_FILE_EXTENSION);
     }
 
@@ -461,7 +462,7 @@
     }
 
     private void removePrintJobFileLocked(PrintJobId printJobId) {
-        File file = generateFileForPrintJob(printJobId);
+        File file = generateFileForPrintJob(PrintSpoolerService.this, printJobId);
         if (file.exists()) {
             file.delete();
             if (DEBUG_PRINT_JOB_LIFECYCLE) {
@@ -557,7 +558,7 @@
     }
 
     private boolean isObsoleteState(int printJobState) {
-        return (isTeminalState(printJobState)
+        return (isTerminalState(printJobState)
                 || printJobState == PrintJobInfo.STATE_QUEUED);
     }
 
@@ -574,7 +575,7 @@
                 || printJobState == PrintJobInfo.STATE_BLOCKED;
     }
 
-    private boolean isTeminalState(int printJobState) {
+    private boolean isTerminalState(int printJobState) {
         return printJobState == PrintJobInfo.STATE_COMPLETED
                 || printJobState == PrintJobInfo.STATE_CANCELED;
     }
@@ -619,61 +620,23 @@
         }
     }
 
-    public void setPrintJobCopiesNoPersistence(PrintJobId printJobId, int copies) {
+    public void updatePrintJobUserConfigurableOptionsNoPersistence(PrintJobInfo printJob) {
         synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setCopies(copies);
+            final int printJobCount = mPrintJobs.size();
+            for (int i = 0; i < printJobCount; i++) {
+                PrintJobInfo cachedPrintJob = mPrintJobs.get(i);
+                if (cachedPrintJob.getId().equals(printJob.getId())) {
+                    cachedPrintJob.setPrinterId(printJob.getPrinterId());
+                    cachedPrintJob.setPrinterName(printJob.getPrinterName());
+                    cachedPrintJob.setCopies(printJob.getCopies());
+                    cachedPrintJob.setDocumentInfo(printJob.getDocumentInfo());
+                    cachedPrintJob.setPages(printJob.getPages());
+                    cachedPrintJob.setAttributes(printJob.getAttributes());
+                    cachedPrintJob.setAdvancedOptions(printJob.getAdvancedOptions());
+                    return;
+                }
             }
-        }
-    }
-
-    public void setPrintJobAdvancedOptionsNoPersistence(PrintJobId printJobId,
-            Bundle advancedOptions) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setAdvancedOptions(advancedOptions);
-            }
-        }
-    }
-
-    public void setPrintJobPrintDocumentInfoNoPersistence(PrintJobId printJobId,
-            PrintDocumentInfo info) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setDocumentInfo(info);
-            }
-        }
-    }
-
-    public void setPrintJobAttributesNoPersistence(PrintJobId printJobId,
-            PrintAttributes attributes) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setAttributes(attributes);
-            }
-        }
-    }
-
-    public void setPrintJobPrinterNoPersistence(PrintJobId printJobId, PrinterInfo printer) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setPrinterId(printer.getId());
-                printJob.setPrinterName(printer.getName());
-            }
-        }
-    }
-
-    public void setPrintJobPagesNoPersistence(PrintJobId printJobId, PageRange[] pages) {
-        synchronized (mLock) {
-            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
-            if (printJob != null) {
-                printJob.setPages(pages);
-            }
+            throw new IllegalArgumentException("No print job with id:" + printJob.getId());
         }
     }
 
@@ -1250,7 +1213,7 @@
         }
     }
 
-    final class PrintSpooler extends IPrintSpooler.Stub {
+    public final class PrintSpooler extends IPrintSpooler.Stub {
         @Override
         public void getPrintJobInfos(IPrintSpoolerCallbacks callback,
                 ComponentName componentName, int state, int appId, int sequence)
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
new file mode 100644
index 0000000..e70c361
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
@@ -0,0 +1,1148 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.printspooler.model;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder.DeathRecipient;
+import android.os.ICancellationSignal;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.print.ILayoutResultCallback;
+import android.print.IPrintDocumentAdapter;
+import android.print.IPrintDocumentAdapterObserver;
+import android.print.IWriteResultCallback;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintDocumentAdapter;
+import android.print.PrintDocumentInfo;
+import android.util.Log;
+
+import com.android.printspooler.R;
+import com.android.printspooler.util.PageRangeUtils;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+
+public final class RemotePrintDocument {
+    private static final String LOG_TAG = "RemotePrintDocument";
+
+    private static final boolean DEBUG = false;
+
+    private static final int STATE_INITIAL = 0;
+    private static final int STATE_STARTED = 1;
+    private static final int STATE_UPDATING = 2;
+    private static final int STATE_UPDATED = 3;
+    private static final int STATE_FAILED = 4;
+    private static final int STATE_FINISHED = 5;
+    private static final int STATE_CANCELING = 6;
+    private static final int STATE_CANCELED = 7;
+    private static final int STATE_DESTROYED = 8;
+
+    private static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {
+            PageRange.ALL_PAGES
+    };
+
+    private final Context mContext;
+
+    private final RemotePrintDocumentInfo mDocumentInfo;
+    private final UpdateSpec mUpdateSpec = new UpdateSpec();
+
+    private final Looper mLooper;
+    private final IPrintDocumentAdapter mPrintDocumentAdapter;
+    private final DocumentObserver mDocumentObserver;
+
+    private final UpdateResultCallbacks mUpdateCallbacks;
+
+    private final CommandDoneCallback mCommandResultCallback =
+            new CommandDoneCallback() {
+        @Override
+        public void onDone() {
+            if (mCurrentCommand.isCompleted()) {
+                if (mCurrentCommand instanceof LayoutCommand) {
+                    // If there is a next command after a layout is done, then another
+                    // update was issued and the next command is another layout, so we
+                    // do nothing. However, if there is no next command we may need to
+                    // ask for some pages given we do not already have them or we do
+                    // but the content has changed.
+                    LayoutCommand layoutCommand = (LayoutCommand) mCurrentCommand;
+                    if (mNextCommand == null) {
+                        if (layoutCommand.isDocumentChanged() || !PageRangeUtils.contains(
+                                mDocumentInfo.writtenPages, mUpdateSpec.pages)) {
+                            mNextCommand = new WriteCommand(mContext, mLooper,
+                                    mPrintDocumentAdapter, mDocumentInfo,
+                                    mDocumentInfo.info.getPageCount(), mUpdateSpec.pages,
+                                    mDocumentInfo.file, mCommandResultCallback);
+                        } else {
+                            // If we have the requested pages just update that ones to be printed.
+                            mDocumentInfo.printedPages = computePrintedPages(mUpdateSpec.pages,
+                                    mDocumentInfo.writtenPages, mDocumentInfo.info.getPageCount());
+                            // Notify we are done.
+                            notifyUpdateCompleted();
+                        }
+                    }
+                } else {
+                    // We always notify after a write.
+                    notifyUpdateCompleted();
+                }
+                runPendingCommand();
+            } else if (mCurrentCommand.isFailed()) {
+                mState = STATE_FAILED;
+                CharSequence error = mCurrentCommand.getError();
+                mCurrentCommand = null;
+                mNextCommand = null;
+                mUpdateSpec.reset();
+                notifyUpdateFailed(error);
+            } else if (mCurrentCommand.isCanceled()) {
+                if (mState == STATE_CANCELING) {
+                    mState = STATE_CANCELED;
+                    notifyUpdateCanceled();
+                }
+                runPendingCommand();
+            }
+        }
+    };
+
+    private final DeathRecipient mDeathRecipient = new DeathRecipient() {
+        @Override
+        public void binderDied() {
+            finish();
+        }
+    };
+
+    private int mState = STATE_INITIAL;
+
+    private AsyncCommand mCurrentCommand;
+    private AsyncCommand mNextCommand;
+
+    public interface DocumentObserver {
+        public void onDestroy();
+    }
+
+    public interface UpdateResultCallbacks {
+        public void onUpdateCompleted(RemotePrintDocumentInfo document);
+        public void onUpdateCanceled();
+        public void onUpdateFailed(CharSequence error);
+    }
+
+    public RemotePrintDocument(Context context, IPrintDocumentAdapter adapter,
+            File file, DocumentObserver destroyListener, UpdateResultCallbacks callbacks) {
+        mPrintDocumentAdapter = adapter;
+        mLooper = context.getMainLooper();
+        mContext = context;
+        mDocumentObserver = destroyListener;
+        mDocumentInfo = new RemotePrintDocumentInfo();
+        mDocumentInfo.file = file;
+        mUpdateCallbacks = callbacks;
+        connectToRemoteDocument();
+    }
+
+    public void start() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "[CALLED] start()");
+        }
+        if (mState != STATE_INITIAL) {
+            throw new IllegalStateException("Cannot start in state:" + stateToString(mState));
+        }
+        try {
+            mPrintDocumentAdapter.start();
+            mState = STATE_STARTED;
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error calling start()", re);
+            mState = STATE_FAILED;
+            mDocumentObserver.onDestroy();
+        }
+    }
+
+    public boolean update(PrintAttributes attributes, PageRange[] pages, boolean preview) {
+        boolean willUpdate;
+
+        if (DEBUG) {
+            Log.i(LOG_TAG, "[CALLED] update()");
+        }
+
+        if (hasUpdateError()) {
+            throw new IllegalStateException("Cannot update without a clearing the failure");
+        }
+
+        if (mState == STATE_INITIAL || mState == STATE_FINISHED || mState == STATE_DESTROYED) {
+            throw new IllegalStateException("Cannot update in state:" + stateToString(mState));
+        }
+
+        // We schedule a layout if the constraints changed.
+        if (!mUpdateSpec.hasSameConstraints(attributes, preview)) {
+            willUpdate = true;
+
+            // If there is a current command that is running we ask for a
+            // cancellation and start over.
+            if (mCurrentCommand != null && (mCurrentCommand.isRunning()
+                    || mCurrentCommand.isPending())) {
+                mCurrentCommand.cancel();
+            }
+
+            // Schedule a layout command.
+            PrintAttributes oldAttributes = mDocumentInfo.attributes != null
+                    ? mDocumentInfo.attributes : new PrintAttributes.Builder().build();
+            AsyncCommand command = new LayoutCommand(mLooper, mPrintDocumentAdapter,
+                  mDocumentInfo, oldAttributes, attributes, preview, mCommandResultCallback);
+            scheduleCommand(command);
+
+            mState = STATE_UPDATING;
+        // If no layout in progress and we don't have all pages - schedule a write.
+        } else if ((!(mCurrentCommand instanceof LayoutCommand)
+                || (!mCurrentCommand.isPending() && !mCurrentCommand.isRunning()))
+                && !PageRangeUtils.contains(mUpdateSpec.pages, pages)) {
+            willUpdate = true;
+
+            // Cancel the current write as a new one is to be scheduled.
+            if (mCurrentCommand instanceof WriteCommand
+                    && (mCurrentCommand.isPending() || mCurrentCommand.isRunning())) {
+                mCurrentCommand.cancel();
+            }
+
+            // Schedule a write command.
+            AsyncCommand command = new WriteCommand(mContext, mLooper, mPrintDocumentAdapter,
+                    mDocumentInfo, mDocumentInfo.info.getPageCount(), pages,
+                    mDocumentInfo.file, mCommandResultCallback);
+            scheduleCommand(command);
+
+            mState = STATE_UPDATING;
+        } else {
+            willUpdate = false;
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[SKIPPING] No update needed");
+            }
+        }
+
+        // Keep track of what is requested.
+        mUpdateSpec.update(attributes, preview, pages);
+
+        runPendingCommand();
+
+        return willUpdate;
+    }
+
+    public void finish() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "[CALLED] finish()");
+        }
+        if (mState != STATE_STARTED && mState != STATE_UPDATED
+                && mState != STATE_FAILED && mState != STATE_CANCELING) {
+            throw new IllegalStateException("Cannot finish in state:"
+                    + stateToString(mState));
+        }
+        try {
+            mPrintDocumentAdapter.finish();
+            mState = STATE_FINISHED;
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error calling finish()", re);
+            mState = STATE_FAILED;
+            mDocumentObserver.onDestroy();
+        }
+    }
+
+    public void cancel() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "[CALLED] cancel()");
+        }
+
+        if (mState == STATE_CANCELING) {
+            return;
+        }
+
+        if (mState != STATE_UPDATING) {
+            throw new IllegalStateException("Cannot cancel in state:" + stateToString(mState));
+        }
+
+        mState = STATE_CANCELING;
+
+        mCurrentCommand.cancel();
+    }
+
+    public void destroy() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "[CALLED] destroy()");
+        }
+        if (mState == STATE_DESTROYED) {
+            throw new IllegalStateException("Cannot destroy in state:" + stateToString(mState));
+        }
+
+        mState = STATE_DESTROYED;
+
+        disconnectFromRemoteDocument();
+        mDocumentObserver.onDestroy();
+    }
+
+    public boolean isUpdating() {
+        return mState == STATE_UPDATING || mState == STATE_CANCELING;
+    }
+
+    public boolean isDestroyed() {
+        return mState == STATE_DESTROYED;
+    }
+
+    public boolean hasUpdateError() {
+        return mState == STATE_FAILED;
+    }
+
+    public void clearUpdateError() {
+        if (!hasUpdateError()) {
+            throw new IllegalStateException("No update error to clear");
+        }
+        mState = STATE_STARTED;
+    }
+
+    public RemotePrintDocumentInfo getDocumentInfo() {
+        return mDocumentInfo;
+    }
+
+    public void writeContent(ContentResolver contentResolver, Uri uri) {
+        InputStream in = null;
+        OutputStream out = null;
+        try {
+            in = new FileInputStream(mDocumentInfo.file);
+            out = contentResolver.openOutputStream(uri);
+            final byte[] buffer = new byte[8192];
+            while (true) {
+                final int readByteCount = in.read(buffer);
+                if (readByteCount < 0) {
+                    break;
+                }
+                out.write(buffer, 0, readByteCount);
+            }
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Error writing document content.", e);
+        } finally {
+            IoUtils.closeQuietly(in);
+            IoUtils.closeQuietly(out);
+        }
+    }
+
+    private void notifyUpdateCanceled() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "[CALLING] onUpdateCanceled()");
+        }
+        mUpdateCallbacks.onUpdateCanceled();
+    }
+
+    private void notifyUpdateCompleted() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "[CALLING] onUpdateCompleted()");
+        }
+        mUpdateCallbacks.onUpdateCompleted(mDocumentInfo);
+    }
+
+    private void notifyUpdateFailed(CharSequence error) {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "[CALLING] onUpdateCompleted()");
+        }
+        mUpdateCallbacks.onUpdateFailed(error);
+    }
+
+    private void connectToRemoteDocument() {
+        try {
+            mPrintDocumentAdapter.asBinder().linkToDeath(mDeathRecipient, 0);
+        } catch (RemoteException re) {
+            Log.w(LOG_TAG, "The printing process is dead.");
+            destroy();
+            return;
+        }
+
+        try {
+            mPrintDocumentAdapter.setObserver(new PrintDocumentAdapterObserver(this));
+        } catch (RemoteException re) {
+            Log.w(LOG_TAG, "Error setting observer to the print adapter.");
+            destroy();
+        }
+    }
+
+    private void disconnectFromRemoteDocument() {
+        try {
+            mPrintDocumentAdapter.setObserver(null);
+        } catch (RemoteException re) {
+            Log.w(LOG_TAG, "Error setting observer to the print adapter.");
+            // Keep going - best effort...
+        }
+
+        mPrintDocumentAdapter.asBinder().unlinkToDeath(mDeathRecipient, 0);
+    }
+
+    private void scheduleCommand(AsyncCommand command) {
+        if (mCurrentCommand == null) {
+            mCurrentCommand = command;
+        } else {
+            mNextCommand = command;
+        }
+    }
+
+    private void runPendingCommand() {
+        if (mCurrentCommand != null
+                && (mCurrentCommand.isCompleted()
+                        || mCurrentCommand.isCanceled())) {
+            mCurrentCommand = mNextCommand;
+            mNextCommand = null;
+        }
+
+        if (mCurrentCommand != null) {
+            if (mCurrentCommand.isPending()) {
+                mCurrentCommand.run();
+            }
+            mState = STATE_UPDATING;
+        } else {
+            mState = STATE_UPDATED;
+        }
+    }
+
+    private static String stateToString(int state) {
+        switch (state) {
+            case STATE_FINISHED: {
+                return "STATE_FINISHED";
+            }
+            case STATE_FAILED: {
+                return "STATE_FAILED";
+            }
+            case STATE_STARTED: {
+                return "STATE_STARTED";
+            }
+            case STATE_UPDATING: {
+                return "STATE_UPDATING";
+            }
+            case STATE_UPDATED: {
+                return "STATE_UPDATED";
+            }
+            case STATE_CANCELING: {
+                return "STATE_CANCELING";
+            }
+            case STATE_CANCELED: {
+                return "STATE_CANCELED";
+            }
+            case STATE_DESTROYED: {
+                return "STATE_DESTROYED";
+            }
+            default: {
+                return "STATE_UNKNOWN";
+            }
+        }
+    }
+
+    private static PageRange[] computePrintedPages(PageRange[] requestedPages,
+                                                   PageRange[] writtenPages, int pageCount) {
+        // Adjust the print job pages based on what was requested and written.
+        // The cases are ordered in the most expected to the least expected.
+        if (Arrays.equals(writtenPages, requestedPages)) {
+            // We got a document with exactly the pages we wanted. Hence,
+            // the printer has to print all pages in the data.
+            return ALL_PAGES_ARRAY;
+        } else if (Arrays.equals(writtenPages, ALL_PAGES_ARRAY)) {
+            // We requested specific pages but got all of them. Hence,
+            // the printer has to print only the requested pages.
+            return requestedPages;
+        } else if (PageRangeUtils.contains(writtenPages, requestedPages)) {
+            // We requested specific pages and got more but not all pages.
+            // Hence, we have to offset appropriately the printed pages to
+            // be based off the start of the written ones instead of zero.
+            // The written pages are always non-null and not empty.
+            final int offset = -writtenPages[0].getStart();
+            PageRangeUtils.offset(requestedPages, offset);
+            return requestedPages;
+        } else if (Arrays.equals(requestedPages, ALL_PAGES_ARRAY)
+                && isAllPages(writtenPages, pageCount)) {
+            // We requested all pages via the special constant and got all
+            // of them as an explicit enumeration. Hence, the printer has
+            // to print only the requested pages.
+            return ALL_PAGES_ARRAY;
+        }
+
+        return null;
+    }
+
+    private static boolean isAllPages(PageRange[] pageRanges, int pageCount) {
+        return pageRanges.length > 0 && pageRanges[0].getStart() == 0
+                && pageRanges[pageRanges.length - 1].getEnd() == pageCount - 1;
+    }
+
+    static final class UpdateSpec {
+        final PrintAttributes attributes = new PrintAttributes.Builder().build();
+        boolean preview;
+        PageRange[] pages;
+
+        public void update(PrintAttributes attributes, boolean preview,
+                PageRange[] pages) {
+            this.attributes.copyFrom(attributes);
+            this.preview = preview;
+            this.pages = Arrays.copyOf(pages, pages.length);
+        }
+
+        public void reset() {
+            attributes.clear();
+            preview = false;
+            pages = null;
+        }
+
+        public boolean hasSameConstraints(PrintAttributes attributes, boolean preview) {
+            return this.attributes.equals(attributes) && this.preview == preview;
+        }
+    }
+
+    public static final class RemotePrintDocumentInfo {
+        public PrintAttributes attributes;
+        public Bundle metadata;
+        public PrintDocumentInfo info;
+        public PageRange[] printedPages;
+        public PageRange[] writtenPages;
+        public File file;
+    }
+
+    private interface CommandDoneCallback {
+        public void onDone();
+    }
+
+    private static abstract class AsyncCommand implements Runnable {
+        private static final int STATE_PENDING = 0;
+        private static final int STATE_RUNNING = 1;
+        private static final int STATE_COMPLETED = 2;
+        private static final int STATE_CANCELED = 3;
+        private static final int STATE_CANCELING = 4;
+        private static final int STATE_FAILED = 5;
+
+        private static int sSequenceCounter;
+
+        protected final int mSequence = sSequenceCounter++;
+        protected final IPrintDocumentAdapter mAdapter;
+        protected final RemotePrintDocumentInfo mDocument;
+
+        protected final CommandDoneCallback mDoneCallback;
+
+        protected ICancellationSignal mCancellation;
+
+        private CharSequence mError;
+
+        private int mState = STATE_PENDING;
+
+        public AsyncCommand(IPrintDocumentAdapter adapter, RemotePrintDocumentInfo document,
+                CommandDoneCallback doneCallback) {
+            mAdapter = adapter;
+            mDocument = document;
+            mDoneCallback = doneCallback;
+        }
+
+        protected final boolean isCanceling() {
+            return mState == STATE_CANCELING;
+        }
+
+        public final boolean isCanceled() {
+            return mState == STATE_CANCELED;
+        }
+
+        public final void cancel() {
+            if (isRunning()) {
+                canceling();
+                if (mCancellation != null) {
+                    try {
+                        mCancellation.cancel();
+                    } catch (RemoteException re) {
+                        Log.w(LOG_TAG, "Error while canceling", re);
+                    }
+                }
+            } else {
+                canceled();
+
+                // Done.
+                mDoneCallback.onDone();
+            }
+        }
+
+        protected final void canceling() {
+            if (mState != STATE_PENDING && mState != STATE_RUNNING) {
+                throw new IllegalStateException("Command not pending or running.");
+            }
+            mState = STATE_CANCELING;
+        }
+
+        protected final void canceled() {
+            if (mState != STATE_CANCELING) {
+                throw new IllegalStateException("Not canceling.");
+            }
+            mState = STATE_CANCELED;
+        }
+
+        public final boolean isPending() {
+            return mState == STATE_PENDING;
+        }
+
+        protected final void running() {
+            if (mState != STATE_PENDING) {
+                throw new IllegalStateException("Not pending.");
+            }
+            mState = STATE_RUNNING;
+        }
+
+        public final boolean isRunning() {
+            return mState == STATE_RUNNING;
+        }
+
+        protected final void completed() {
+            if (mState != STATE_RUNNING && mState != STATE_CANCELING) {
+                throw new IllegalStateException("Not running.");
+            }
+            mState = STATE_COMPLETED;
+        }
+
+        public final boolean isCompleted() {
+            return mState == STATE_COMPLETED;
+        }
+
+        protected final void failed(CharSequence error) {
+            if (mState != STATE_RUNNING) {
+                throw new IllegalStateException("Not running.");
+            }
+            mState = STATE_FAILED;
+
+            mError = error;
+        }
+
+        public final boolean isFailed() {
+            return mState == STATE_FAILED;
+        }
+
+        public CharSequence getError() {
+            return mError;
+        }
+    }
+
+    private static final class LayoutCommand extends AsyncCommand {
+        private final PrintAttributes mOldAttributes = new PrintAttributes.Builder().build();
+        private final PrintAttributes mNewAttributes = new PrintAttributes.Builder().build();
+        private final Bundle mMetadata = new Bundle();
+
+        private final ILayoutResultCallback mRemoteResultCallback;
+
+        private final Handler mHandler;
+
+        private boolean mDocumentChanged;
+
+        public LayoutCommand(Looper looper, IPrintDocumentAdapter adapter,
+                RemotePrintDocumentInfo document, PrintAttributes oldAttributes,
+                PrintAttributes newAttributes, boolean preview, CommandDoneCallback callback) {
+            super(adapter, document, callback);
+            mHandler = new LayoutHandler(looper);
+            mRemoteResultCallback = new LayoutResultCallback(mHandler);
+            mOldAttributes.copyFrom(oldAttributes);
+            mNewAttributes.copyFrom(newAttributes);
+            mMetadata.putBoolean(PrintDocumentAdapter.EXTRA_PRINT_PREVIEW, preview);
+        }
+
+        public boolean isDocumentChanged() {
+            return mDocumentChanged;
+        }
+
+        @Override
+        public void run() {
+            running();
+
+            try {
+                if (DEBUG) {
+                    Log.i(LOG_TAG, "[PERFORMING] layout");
+                }
+                mAdapter.layout(mOldAttributes, mNewAttributes, mRemoteResultCallback,
+                        mMetadata, mSequence);
+            } catch (RemoteException re) {
+                Log.e(LOG_TAG, "Error calling layout", re);
+                handleOnLayoutFailed(null, mSequence);
+            }
+        }
+
+        private void handleOnLayoutStarted(ICancellationSignal cancellation, int sequence) {
+            if (sequence != mSequence) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[CALLBACK] onLayoutStarted");
+            }
+
+            if (isCanceling()) {
+                try {
+                    cancellation.cancel();
+                } catch (RemoteException re) {
+                    Log.e(LOG_TAG, "Error cancelling", re);
+                    handleOnLayoutFailed(null, mSequence);
+                }
+            } else {
+                mCancellation = cancellation;
+            }
+        }
+
+        private void handleOnLayoutFinished(PrintDocumentInfo info,
+                boolean changed, int sequence) {
+            if (sequence != mSequence) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[CALLBACK] onLayoutFinished");
+            }
+
+            completed();
+
+            // If the document description changed or the content in the
+            // document changed, the we need to invalidate the pages.
+            if (changed || !equalsIgnoreSize(mDocument.info, info)) {
+                // If the content changed we throw away all pages as
+                // we will request them again with the new content.
+                mDocument.writtenPages = null;
+                mDocument.printedPages = null;
+                mDocumentChanged = true;
+            }
+
+            // Update the document with data from the layout pass.
+            mDocument.attributes = mNewAttributes;
+            mDocument.metadata = mMetadata;
+            mDocument.info = info;
+
+            // Release the remote cancellation interface.
+            mCancellation = null;
+
+            // Done.
+            mDoneCallback.onDone();
+        }
+
+        private void handleOnLayoutFailed(CharSequence error, int sequence) {
+            if (sequence != mSequence) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[CALLBACK] onLayoutFailed");
+            }
+
+            failed(error);
+
+            // Release the remote cancellation interface.
+            mCancellation = null;
+
+            // Failed.
+            mDoneCallback.onDone();
+        }
+
+        private void handleOnLayoutCanceled(int sequence) {
+            if (sequence != mSequence) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[CALLBACK] onLayoutCanceled");
+            }
+
+            canceled();
+
+            // Release the remote cancellation interface.
+            mCancellation = null;
+
+            // Done.
+            mDoneCallback.onDone();
+        }
+
+        private boolean equalsIgnoreSize(PrintDocumentInfo lhs, PrintDocumentInfo rhs) {
+            if (lhs == rhs) {
+                return true;
+            }
+            if (lhs == null) {
+                return false;
+            } else {
+                if (rhs == null) {
+                    return false;
+                }
+                if (lhs.getContentType() != rhs.getContentType()
+                        || lhs.getPageCount() != rhs.getPageCount()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private final class LayoutHandler extends Handler {
+            public static final int MSG_ON_LAYOUT_STARTED = 1;
+            public static final int MSG_ON_LAYOUT_FINISHED = 2;
+            public static final int MSG_ON_LAYOUT_FAILED = 3;
+            public static final int MSG_ON_LAYOUT_CANCELED = 4;
+
+            public LayoutHandler(Looper looper) {
+                super(looper, null, false);
+            }
+
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MSG_ON_LAYOUT_STARTED: {
+                        ICancellationSignal cancellation = (ICancellationSignal) message.obj;
+                        final int sequence = message.arg1;
+                        handleOnLayoutStarted(cancellation, sequence);
+                    } break;
+
+                    case MSG_ON_LAYOUT_FINISHED: {
+                        PrintDocumentInfo info = (PrintDocumentInfo) message.obj;
+                        final boolean changed = (message.arg1 == 1);
+                        final int sequence = message.arg2;
+                        handleOnLayoutFinished(info, changed, sequence);
+                    } break;
+
+                    case MSG_ON_LAYOUT_FAILED: {
+                        CharSequence error = (CharSequence) message.obj;
+                        final int sequence = message.arg1;
+                        handleOnLayoutFailed(error, sequence);
+                    } break;
+
+                    case MSG_ON_LAYOUT_CANCELED: {
+                        final int sequence = message.arg1;
+                        handleOnLayoutCanceled(sequence);
+                    } break;
+                }
+            }
+        }
+
+        private static final class LayoutResultCallback extends ILayoutResultCallback.Stub {
+            private final WeakReference<Handler> mWeakHandler;
+
+            public LayoutResultCallback(Handler handler) {
+                mWeakHandler = new WeakReference<>(handler);
+            }
+
+            @Override
+            public void onLayoutStarted(ICancellationSignal cancellation, int sequence) {
+                Handler handler = mWeakHandler.get();
+                if (handler != null) {
+                    handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_STARTED,
+                            sequence, 0, cancellation).sendToTarget();
+                }
+            }
+
+            @Override
+            public void onLayoutFinished(PrintDocumentInfo info, boolean changed, int sequence) {
+                Handler handler = mWeakHandler.get();
+                if (handler != null) {
+                    handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_FINISHED,
+                            changed ? 1 : 0, sequence, info).sendToTarget();
+                }
+            }
+
+            @Override
+            public void onLayoutFailed(CharSequence error, int sequence) {
+                Handler handler = mWeakHandler.get();
+                if (handler != null) {
+                    handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_FAILED,
+                            sequence, 0, error).sendToTarget();
+                }
+            }
+
+            @Override
+            public void onLayoutCanceled(int sequence) {
+                Handler handler = mWeakHandler.get();
+                if (handler != null) {
+                    handler.obtainMessage(LayoutHandler.MSG_ON_LAYOUT_CANCELED,
+                            sequence, 0).sendToTarget();
+                }
+            }
+        }
+    }
+
+    private static final class WriteCommand extends AsyncCommand {
+        private final int mPageCount;
+        private final PageRange[] mPages;
+        private final File mContentFile;
+
+        private final IWriteResultCallback mRemoteResultCallback;
+        private final CommandDoneCallback mDoneCallback;
+
+        private final Context mContext;
+        private final Handler mHandler;
+
+        public WriteCommand(Context context, Looper looper, IPrintDocumentAdapter adapter,
+                RemotePrintDocumentInfo document, int pageCount, PageRange[] pages,
+                File contentFile, CommandDoneCallback callback) {
+            super(adapter, document, callback);
+            mContext = context;
+            mHandler = new WriteHandler(looper);
+            mRemoteResultCallback = new WriteResultCallback(mHandler);
+            mPageCount = pageCount;
+            mPages = Arrays.copyOf(pages, pages.length);
+            mContentFile = contentFile;
+            mDoneCallback = callback;
+        }
+
+        @Override
+        public void run() {
+            running();
+
+            // This is a long running operation as we will be reading fully
+            // the written data. In case of a cancellation, we ask the client
+            // to stop writing data and close the file descriptor after
+            // which we will reach the end of the stream, thus stop reading.
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... params) {
+                    InputStream in = null;
+                    OutputStream out = null;
+                    ParcelFileDescriptor source = null;
+                    ParcelFileDescriptor sink = null;
+                    try {
+                        ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+                        source = pipe[0];
+                        sink = pipe[1];
+
+                        in = new FileInputStream(source.getFileDescriptor());
+                        out = new FileOutputStream(mContentFile);
+
+                        // Async call to initiate the other process writing the data.
+                        if (DEBUG) {
+                            Log.i(LOG_TAG, "[PERFORMING] write");
+                        }
+                        mAdapter.write(mPages, sink, mRemoteResultCallback, mSequence);
+
+                        // Close the source. It is now held by the client.
+                        sink.close();
+                        sink = null;
+
+                        // Read the data.
+                        final byte[] buffer = new byte[8192];
+                        while (true) {
+                            final int readByteCount = in.read(buffer);
+                            if (readByteCount < 0) {
+                                break;
+                            }
+                            out.write(buffer, 0, readByteCount);
+                        }
+                    } catch (RemoteException | IOException e) {
+                        Log.e(LOG_TAG, "Error calling write()", e);
+                    } finally {
+                        IoUtils.closeQuietly(in);
+                        IoUtils.closeQuietly(out);
+                        IoUtils.closeQuietly(sink);
+                        IoUtils.closeQuietly(source);
+                    }
+                    return null;
+                }
+            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+        }
+
+        private void handleOnWriteStarted(ICancellationSignal cancellation, int sequence) {
+            if (sequence != mSequence) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[CALLBACK] onWriteStarted");
+            }
+
+            if (isCanceling()) {
+                try {
+                    cancellation.cancel();
+                } catch (RemoteException re) {
+                    Log.e(LOG_TAG, "Error cancelling", re);
+                    handleOnWriteFailed(null, sequence);
+                }
+            } else {
+                mCancellation = cancellation;
+            }
+        }
+
+        private void handleOnWriteFinished(PageRange[] pages, int sequence) {
+            if (sequence != mSequence) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[CALLBACK] onWriteFinished");
+            }
+
+            PageRange[] writtenPages = PageRangeUtils.normalize(pages);
+            PageRange[] printedPages = computePrintedPages(mPages, writtenPages, mPageCount);
+
+            // Handle if we got invalid pages
+            if (printedPages != null) {
+                mDocument.writtenPages = writtenPages;
+                mDocument.printedPages = printedPages;
+                completed();
+            } else {
+                mDocument.writtenPages = null;
+                mDocument.printedPages = null;
+                failed(mContext.getString(R.string.print_error_default_message));
+            }
+
+            // Release the remote cancellation interface.
+            mCancellation = null;
+
+            // Done.
+            mDoneCallback.onDone();
+        }
+
+        private void handleOnWriteFailed(CharSequence error, int sequence) {
+            if (sequence != mSequence) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[CALLBACK] onWriteFailed");
+            }
+
+            failed(error);
+
+            // Release the remote cancellation interface.
+            mCancellation = null;
+
+            // Done.
+            mDoneCallback.onDone();
+        }
+
+        private void handleOnWriteCanceled(int sequence) {
+            if (sequence != mSequence) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[CALLBACK] onWriteCanceled");
+            }
+
+            canceled();
+
+            // Release the remote cancellation interface.
+            mCancellation = null;
+
+            // Done.
+            mDoneCallback.onDone();
+        }
+
+        private final class WriteHandler extends Handler {
+            public static final int MSG_ON_WRITE_STARTED = 1;
+            public static final int MSG_ON_WRITE_FINISHED = 2;
+            public static final int MSG_ON_WRITE_FAILED = 3;
+            public static final int MSG_ON_WRITE_CANCELED = 4;
+
+            public WriteHandler(Looper looper) {
+                super(looper, null, false);
+            }
+
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MSG_ON_WRITE_STARTED: {
+                        ICancellationSignal cancellation = (ICancellationSignal) message.obj;
+                        final int sequence = message.arg1;
+                        handleOnWriteStarted(cancellation, sequence);
+                    } break;
+
+                    case MSG_ON_WRITE_FINISHED: {
+                        PageRange[] pages = (PageRange[]) message.obj;
+                        final int sequence = message.arg1;
+                        handleOnWriteFinished(pages, sequence);
+                    } break;
+
+                    case MSG_ON_WRITE_FAILED: {
+                        CharSequence error = (CharSequence) message.obj;
+                        final int sequence = message.arg1;
+                        handleOnWriteFailed(error, sequence);
+                    } break;
+
+                    case MSG_ON_WRITE_CANCELED: {
+                        final int sequence = message.arg1;
+                        handleOnWriteCanceled(sequence);
+                    } break;
+                }
+            }
+        }
+
+        private static final class WriteResultCallback extends IWriteResultCallback.Stub {
+            private final WeakReference<Handler> mWeakHandler;
+
+            public WriteResultCallback(Handler handler) {
+                mWeakHandler = new WeakReference<>(handler);
+            }
+
+            @Override
+            public void onWriteStarted(ICancellationSignal cancellation, int sequence) {
+                Handler handler = mWeakHandler.get();
+                if (handler != null) {
+                    handler.obtainMessage(WriteHandler.MSG_ON_WRITE_STARTED,
+                            sequence, 0, cancellation).sendToTarget();
+                }
+            }
+
+            @Override
+            public void onWriteFinished(PageRange[] pages, int sequence) {
+                Handler handler = mWeakHandler.get();
+                if (handler != null) {
+                    handler.obtainMessage(WriteHandler.MSG_ON_WRITE_FINISHED,
+                            sequence, 0, pages).sendToTarget();
+                }
+            }
+
+            @Override
+            public void onWriteFailed(CharSequence error, int sequence) {
+                Handler handler = mWeakHandler.get();
+                if (handler != null) {
+                    handler.obtainMessage(WriteHandler.MSG_ON_WRITE_FAILED,
+                        sequence, 0, error).sendToTarget();
+                }
+            }
+
+            @Override
+            public void onWriteCanceled(int sequence) {
+                Handler handler = mWeakHandler.get();
+                if (handler != null) {
+                    handler.obtainMessage(WriteHandler.MSG_ON_WRITE_CANCELED,
+                        sequence, 0).sendToTarget();
+                }
+            }
+        }
+    }
+
+    private static final class PrintDocumentAdapterObserver
+            extends IPrintDocumentAdapterObserver.Stub {
+        private final WeakReference<RemotePrintDocument> mWeakDocument;
+
+        public PrintDocumentAdapterObserver(RemotePrintDocument document) {
+            mWeakDocument = new WeakReference<>(document);
+        }
+
+        @Override
+        public void onDestroy() {
+            final RemotePrintDocument document = mWeakDocument.get();
+            if (document != null) {
+                new Handler(document.mLooper).post(new Runnable() {
+                    @Override
+                    public void run() {
+                        document.mDocumentObserver.onDestroy();
+                    }
+                });
+            }
+        }
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
similarity index 98%
rename from packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
rename to packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
index 9831839..d802cd8 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.printspooler;
+package com.android.printspooler.ui;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -57,7 +57,7 @@
  * This class is responsible for loading printers by doing discovery
  * and merging the discovered printers with the previously used ones.
  */
-public class FusedPrintersProvider extends Loader<List<PrinterInfo>> {
+public final class FusedPrintersProvider extends Loader<List<PrinterInfo>> {
     private static final String LOG_TAG = "FusedPrintersProvider";
 
     private static final boolean DEBUG = false;
@@ -192,7 +192,7 @@
             for (int i = 0; i < favoriteCount; i++) {
                 printerIds.add(mFavoritePrinters.get(i).getId());
             }
-            mDiscoverySession.startPrinterDisovery(printerIds);
+            mDiscoverySession.startPrinterDiscovery(printerIds);
             List<PrinterInfo> printers = mDiscoverySession.getPrinters();
             if (!printers.isEmpty()) {
                 updatePrinters(printers, mFavoritePrinters);
@@ -281,7 +281,9 @@
                 mDiscoverySession.stopPrinterStateTracking(mTrackedPrinter);
             }
             mTrackedPrinter = printerId;
-            mDiscoverySession.startPrinterStateTracking(printerId);
+            if (printerId != null) {
+                mDiscoverySession.startPrinterStateTracking(printerId);
+            }
         }
     }
 
@@ -514,7 +516,7 @@
             }
 
             private List<PrinterInfo> doReadPrinterHistory() {
-                FileInputStream in = null;
+                final FileInputStream in;
                 try {
                     in = mStatePersistFile.openRead();
                 } catch (FileNotFoundException fnfe) {
@@ -641,7 +643,7 @@
                 }
                 return true;
             }
-        };
+        }
 
         private final class WriteTask extends AsyncTask<List<PrinterInfo>, Void, Void> {
             @Override
@@ -703,6 +705,6 @@
                     IoUtils.closeQuietly(out);
                 }
             }
-        };
+        }
     }
 }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
new file mode 100644
index 0000000..3e0d7e5
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -0,0 +1,1955 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.printspooler.ui;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.print.IPrintDocumentAdapter;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Resolution;
+import android.print.PrintDocumentInfo;
+import android.print.PrintJobInfo;
+import android.print.PrintManager;
+import android.print.PrinterCapabilitiesInfo;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.printservice.PrintService;
+import android.provider.DocumentsContract;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextUtils.SimpleStringSplitter;
+import android.text.TextWatcher;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnFocusChangeListener;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.android.printspooler.R;
+import com.android.printspooler.model.PrintSpoolerProvider;
+import com.android.printspooler.model.PrintSpoolerService;
+import com.android.printspooler.model.RemotePrintDocument;
+import com.android.printspooler.util.MediaSizeUtils;
+import com.android.printspooler.util.MediaSizeUtils.MediaSizeComparator;
+import com.android.printspooler.util.PageRangeUtils;
+import com.android.printspooler.util.PrintOptionUtils;
+import com.android.printspooler.widget.ContentView;
+import com.android.printspooler.widget.ContentView.OptionsStateChangeListener;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class PrintActivity extends Activity implements RemotePrintDocument.UpdateResultCallbacks,
+        PrintErrorFragment.OnActionListener, PrintProgressFragment.OnCancelRequestListener {
+    private static final String LOG_TAG = "PrintActivity";
+
+    public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
+
+    private static final int ORIENTATION_PORTRAIT = 0;
+    private static final int ORIENTATION_LANDSCAPE = 1;
+
+    private static final int ACTIVITY_REQUEST_CREATE_FILE = 1;
+    private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2;
+    private static final int ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS = 3;
+
+    private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9;
+
+    private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE;
+    private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1;
+
+    private static final int STATE_CONFIGURING = 0;
+    private static final int STATE_PRINT_CONFIRMED = 1;
+    private static final int STATE_PRINT_CANCELED = 2;
+    private static final int STATE_UPDATE_FAILED = 3;
+    private static final int STATE_CREATE_FILE_FAILED = 4;
+    private static final int STATE_PRINTER_UNAVAILABLE = 5;
+
+    private static final int UI_STATE_PREVIEW = 0;
+    private static final int UI_STATE_ERROR = 1;
+    private static final int UI_STATE_PROGRESS = 2;
+
+    private static final int MIN_COPIES = 1;
+    private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES);
+
+    private static final Pattern PATTERN_DIGITS = Pattern.compile("[\\d]+");
+
+    private static final Pattern PATTERN_ESCAPE_SPECIAL_CHARS = Pattern.compile(
+            "(?=[]\\[+&|!(){}^\"~*?:\\\\])");
+
+    private static final Pattern PATTERN_PAGE_RANGE = Pattern.compile(
+            "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*?(([,])"
+            + "[\\s]*[0-9]*[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+");
+
+    public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[] {PageRange.ALL_PAGES};
+
+    private final PrinterAvailabilityDetector mPrinterAvailabilityDetector =
+            new PrinterAvailabilityDetector();
+
+    private final SimpleStringSplitter mStringCommaSplitter = new SimpleStringSplitter(',');
+
+    private final OnFocusChangeListener mSelectAllOnFocusListener = new SelectAllOnFocusListener();
+
+    private PrintSpoolerProvider mSpoolerProvider;
+
+    private PrintJobInfo mPrintJob;
+    private RemotePrintDocument mPrintedDocument;
+    private PrinterRegistry mPrinterRegistry;
+
+    private EditText mCopiesEditText;
+
+    private TextView mPageRangeOptionsTitle;
+    private TextView mPageRangeTitle;
+    private EditText mPageRangeEditText;
+
+    private Spinner mDestinationSpinner;
+    private DestinationAdapter mDestinationSpinnerAdapter;
+
+    private Spinner mMediaSizeSpinner;
+    private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
+
+    private Spinner mColorModeSpinner;
+    private ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
+
+    private Spinner mOrientationSpinner;
+    private ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
+
+    private Spinner mRangeOptionsSpinner;
+
+    private ContentView mOptionsContent;
+
+    private TextView mSummaryCopies;
+    private TextView mSummaryPaperSize;
+
+    private View mAdvancedPrintOptionsContainer;
+
+    private Button mMoreOptionsButton;
+
+    private ImageView mPrintButton;
+
+    private ProgressMessageController mProgressMessageController;
+
+    private MediaSizeComparator mMediaSizeComparator;
+
+    private PageRange[] mRequestedPages;
+
+    private PrinterInfo mOldCurrentPrinter;
+
+    private String mCallingPackageName;
+
+    private int mState = STATE_CONFIGURING;
+
+    private int mUiState = UI_STATE_PREVIEW;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Bundle extras = getIntent().getExtras();
+
+        mPrintJob = extras.getParcelable(PrintManager.EXTRA_PRINT_JOB);
+        if (mPrintJob == null) {
+            throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_JOB
+                    + " cannot be null");
+        }
+        mPrintJob.setAttributes(new PrintAttributes.Builder().build());
+
+        final IBinder adapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER);
+        if (adapter == null) {
+            throw new IllegalArgumentException(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER
+                    + " cannot be null");
+        }
+
+        mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME);
+
+        // This will take just a few milliseconds, so just wait to
+        // bind to the local service before showing the UI.
+        mSpoolerProvider = new PrintSpoolerProvider(this,
+                new Runnable() {
+            @Override
+            public void run() {
+                // Now that we are bound to the print spooler service,
+                // create the printer registry and wait for it to get
+                // the first batch of results which will be delivered
+                // after reading historical data. This should be pretty
+                // fast, so just wait before showing the UI.
+                mPrinterRegistry = new PrinterRegistry(PrintActivity.this,
+                        new Runnable() {
+                    @Override
+                    public void run() {
+                        setTitle(R.string.print_dialog);
+                        setContentView(R.layout.print_activity);
+
+                        mPrintedDocument = new RemotePrintDocument(PrintActivity.this,
+                                IPrintDocumentAdapter.Stub.asInterface(adapter),
+                                PrintSpoolerService.generateFileForPrintJob(PrintActivity.this,
+                                        mPrintJob.getId()),
+                                new RemotePrintDocument.DocumentObserver() {
+                                    @Override
+                                    public void onDestroy() {
+                                        finish();
+                                    }
+                                }, PrintActivity.this);
+
+                        mProgressMessageController = new ProgressMessageController(PrintActivity.this);
+
+                        mMediaSizeComparator = new MediaSizeComparator(PrintActivity.this);
+
+                        mDestinationSpinnerAdapter = new DestinationAdapter();
+
+                        bindUi();
+
+                        updateOptionsUi();
+
+                        // Now show the updated UI to avoid flicker.
+                        mOptionsContent.setVisibility(View.VISIBLE);
+
+                        mRequestedPages = computeRequestedPages();
+
+                        mPrintedDocument.start();
+
+                        ensurePreviewUiShown();
+                    }
+                });
+            }
+        });
+    }
+
+    @Override
+    public void onPause() {
+        if (isFinishing()) {
+            PrintSpoolerService spooler = mSpoolerProvider.getSpooler();
+            if (mState == STATE_PRINT_CONFIRMED) {
+                spooler.updatePrintJobUserConfigurableOptionsNoPersistence(mPrintJob);
+                spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_QUEUED, null);
+            } else {
+                spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, null);
+            }
+            mProgressMessageController.cancel();
+            mPrinterRegistry.setTrackedPrinter(null);
+            mSpoolerProvider.destroy();
+            mPrintedDocument.finish();
+            mPrintedDocument.destroy();
+        }
+
+        mPrinterAvailabilityDetector.cancel();
+
+        super.onPause();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            event.startTracking();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK
+                && event.isTracking() && !event.isCanceled()) {
+            cancelPrint();
+            return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override
+    public void onActionPerformed() {
+        switch (mState) {
+            case STATE_UPDATE_FAILED: {
+                if (canUpdateDocument()) {
+                    updateDocument(true, true);
+                    ensurePreviewUiShown();
+                    mState = STATE_CONFIGURING;
+                    updateOptionsUi();
+                }
+            } break;
+
+            case STATE_CREATE_FILE_FAILED: {
+                mState = STATE_CONFIGURING;
+                ensurePreviewUiShown();
+                updateOptionsUi();
+            } break;
+        }
+    }
+
+    @Override
+    public void onCancelRequest() {
+        if (mPrintedDocument.isUpdating()) {
+            mPrintedDocument.cancel();
+        }
+    }
+
+    public void onUpdateCanceled() {
+        mProgressMessageController.cancel();
+        ensurePreviewUiShown();
+        finishIfConfirmedOrCanceled();
+        updateOptionsUi();
+    }
+
+    @Override
+    public void onUpdateCompleted(RemotePrintDocument.RemotePrintDocumentInfo document) {
+        mProgressMessageController.cancel();
+        ensurePreviewUiShown();
+
+        // Update the print job with the info for the written document. The page
+        // count we get from the remote document is the pages in the document from
+        // the app perspective but the print job should contain the page count from
+        // print service perspective which is the pages in the written PDF not the
+        // pages in the printed document.
+        PrintDocumentInfo info = document.info;
+        if (info == null) {
+            return;
+        }
+        final int pageCount = PageRangeUtils.getNormalizedPageCount(document.writtenPages,
+                info.getPageCount());
+        PrintDocumentInfo adjustedInfo = new PrintDocumentInfo.Builder(info.getName())
+                .setContentType(info.getContentType())
+                .setPageCount(pageCount)
+                .build();
+        mPrintJob.setDocumentInfo(adjustedInfo);
+        mPrintJob.setPages(document.printedPages);
+        finishIfConfirmedOrCanceled();
+        updateOptionsUi();
+    }
+
+    @Override
+    public void onUpdateFailed(CharSequence error) {
+        mState = STATE_UPDATE_FAILED;
+        ensureErrorUiShown(error, PrintErrorFragment.ACTION_RETRY);
+        updateOptionsUi();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case ACTIVITY_REQUEST_CREATE_FILE: {
+                onStartCreateDocumentActivityResult(resultCode, data);
+            } break;
+
+            case ACTIVITY_REQUEST_SELECT_PRINTER: {
+                onSelectPrinterActivityResult(resultCode, data);
+            } break;
+
+            case ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS: {
+                onAdvancedPrintOptionsActivityResult(resultCode, data);
+            } break;
+        }
+    }
+
+    private void startCreateDocumentActivity() {
+        PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
+        if (info == null) {
+            return;
+        }
+        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+        intent.setType("application/pdf");
+        intent.putExtra(Intent.EXTRA_TITLE, info.getName());
+        intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName);
+        startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
+    }
+
+    private void onStartCreateDocumentActivityResult(int resultCode, Intent data) {
+        if (resultCode == RESULT_OK && data != null) {
+            Uri uri = data.getData();
+            mPrintedDocument.writeContent(getContentResolver(), uri);
+            finish();
+        } else if (resultCode == RESULT_CANCELED) {
+            mState = STATE_CONFIGURING;
+            updateOptionsUi();
+        } else {
+            ensureErrorUiShown(getString(R.string.print_write_error_message),
+                    PrintErrorFragment.ACTION_CONFIRM);
+            mState = STATE_CREATE_FILE_FAILED;
+            updateOptionsUi();
+        }
+    }
+
+    private void startSelectPrinterActivity() {
+        Intent intent = new Intent(this, SelectPrinterActivity.class);
+        startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER);
+    }
+
+    private void onSelectPrinterActivityResult(int resultCode, Intent data) {
+        if (resultCode == RESULT_OK && data != null) {
+            PrinterId printerId = data.getParcelableExtra(INTENT_EXTRA_PRINTER_ID);
+            if (printerId != null) {
+                mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerId);
+                final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
+                if (index != AdapterView.INVALID_POSITION) {
+                    mDestinationSpinner.setSelection(index);
+                    return;
+                }
+            }
+        }
+
+        PrinterId printerId = mOldCurrentPrinter.getId();
+        final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
+        mDestinationSpinner.setSelection(index);
+    }
+
+    private void startAdvancedPrintOptionsActivity(PrinterInfo printer) {
+        ComponentName serviceName = printer.getId().getServiceName();
+
+        String activityName = PrintOptionUtils.getAdvancedOptionsActivityName(this, serviceName);
+        if (TextUtils.isEmpty(activityName)) {
+            return;
+        }
+
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setComponent(new ComponentName(serviceName.getPackageName(), activityName));
+
+        List<ResolveInfo> resolvedActivities = getPackageManager()
+                .queryIntentActivities(intent, 0);
+        if (resolvedActivities.isEmpty()) {
+            return;
+        }
+
+        // The activity is a component name, therefore it is one or none.
+        if (resolvedActivities.get(0).activityInfo.exported) {
+            intent.putExtra(PrintService.EXTRA_PRINT_JOB_INFO, mPrintJob);
+            intent.putExtra(PrintService.EXTRA_PRINTER_INFO, printer);
+
+            // This is external activity and may not be there.
+            try {
+                startActivityForResult(intent, ACTIVITY_REQUEST_POPULATE_ADVANCED_PRINT_OPTIONS);
+            } catch (ActivityNotFoundException anfe) {
+                Log.e(LOG_TAG, "Error starting activity for intent: " + intent, anfe);
+            }
+        }
+    }
+
+    private void onAdvancedPrintOptionsActivityResult(int resultCode, Intent data) {
+        if (resultCode != RESULT_OK || data == null) {
+            return;
+        }
+
+        PrintJobInfo printJobInfo = data.getParcelableExtra(PrintService.EXTRA_PRINT_JOB_INFO);
+
+        if (printJobInfo == null) {
+            return;
+        }
+
+        // Take the advanced options without interpretation.
+        mPrintJob.setAdvancedOptions(printJobInfo.getAdvancedOptions());
+
+        // Take copies without interpretation as the advanced print dialog
+        // cannot create a print job info with invalid copies.
+        mCopiesEditText.setText(String.valueOf(printJobInfo.getCopies()));
+        mPrintJob.setCopies(printJobInfo.getCopies());
+
+        PrintAttributes currAttributes = mPrintJob.getAttributes();
+        PrintAttributes newAttributes = printJobInfo.getAttributes();
+
+        // Take the media size only if the current printer supports is.
+        MediaSize oldMediaSize = currAttributes.getMediaSize();
+        MediaSize newMediaSize = newAttributes.getMediaSize();
+        if (!oldMediaSize.equals(newMediaSize)) {
+            final int mediaSizeCount = mMediaSizeSpinnerAdapter.getCount();
+            MediaSize newMediaSizePortrait = newAttributes.getMediaSize().asPortrait();
+            for (int i = 0; i < mediaSizeCount; i++) {
+                MediaSize supportedSizePortrait = mMediaSizeSpinnerAdapter.getItem(i).value.asPortrait();
+                if (supportedSizePortrait.equals(newMediaSizePortrait)) {
+                    currAttributes.setMediaSize(newMediaSize);
+                    mMediaSizeSpinner.setSelection(i);
+                    if (currAttributes.getMediaSize().isPortrait()) {
+                        if (mOrientationSpinner.getSelectedItemPosition() != 0) {
+                            mOrientationSpinner.setSelection(0);
+                        }
+                    } else {
+                        if (mOrientationSpinner.getSelectedItemPosition() != 1) {
+                            mOrientationSpinner.setSelection(1);
+                        }
+                    }
+                    break;
+                }
+            }
+        }
+
+        // Take the color mode only if the current printer supports it.
+        final int currColorMode = currAttributes.getColorMode();
+        final int newColorMode = newAttributes.getColorMode();
+        if (currColorMode != newColorMode) {
+            final int colorModeCount = mColorModeSpinner.getCount();
+            for (int i = 0; i < colorModeCount; i++) {
+                final int supportedColorMode = mColorModeSpinnerAdapter.getItem(i).value;
+                if (supportedColorMode == newColorMode) {
+                    currAttributes.setColorMode(newColorMode);
+                    mColorModeSpinner.setSelection(i);
+                    break;
+                }
+            }
+        }
+
+        // Take the page range only if it is valid.
+        PageRange[] pageRanges = printJobInfo.getPages();
+        if (pageRanges != null && pageRanges.length > 0) {
+            pageRanges = PageRangeUtils.normalize(pageRanges);
+
+            PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
+            final int pageCount = (info != null) ? info.getPageCount() : 0;
+
+            // Handle the case where all pages are specified explicitly
+            // instead of the *all pages* constant.
+            if (pageRanges.length == 1) {
+                if (pageRanges[0].getStart() == 0 && pageRanges[0].getEnd() == pageCount - 1) {
+                    pageRanges[0] = PageRange.ALL_PAGES;
+                }
+            }
+
+            if (Arrays.equals(pageRanges, ALL_PAGES_ARRAY)) {
+                mPrintJob.setPages(pageRanges);
+
+                if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
+                    mRangeOptionsSpinner.setSelection(0);
+                }
+            } else if (pageRanges[0].getStart() >= 0
+                    && pageRanges[pageRanges.length - 1].getEnd() < pageCount) {
+                mPrintJob.setPages(pageRanges);
+
+                if (mRangeOptionsSpinner.getSelectedItemPosition() != 1) {
+                    mRangeOptionsSpinner.setSelection(1);
+                }
+
+                StringBuilder builder = new StringBuilder();
+                final int pageRangeCount = pageRanges.length;
+                for (int i = 0; i < pageRangeCount; i++) {
+                    if (builder.length() > 0) {
+                         builder.append(',');
+                    }
+
+                    final int shownStartPage;
+                    final int shownEndPage;
+                    PageRange pageRange = pageRanges[i];
+                    if (pageRange.equals(PageRange.ALL_PAGES)) {
+                        shownStartPage = 1;
+                        shownEndPage = pageCount;
+                    } else {
+                        shownStartPage = pageRange.getStart() + 1;
+                        shownEndPage = pageRange.getEnd() + 1;
+                    }
+
+                    builder.append(shownStartPage);
+
+                    if (shownStartPage != shownEndPage) {
+                        builder.append('-');
+                        builder.append(shownEndPage);
+                    }
+                }
+                mPageRangeEditText.setText(builder.toString());
+            }
+        }
+
+        // Update the content if needed.
+        if (canUpdateDocument()) {
+            updateDocument(true, false);
+        }
+    }
+
+    private void ensureProgressUiShown() {
+        if (mUiState != UI_STATE_PROGRESS) {
+            mUiState = UI_STATE_PROGRESS;
+            Fragment fragment = PrintProgressFragment.newInstance();
+            showFragment(fragment);
+        }
+    }
+
+    private void ensurePreviewUiShown() {
+        if (mUiState != UI_STATE_PREVIEW) {
+            mUiState = UI_STATE_PREVIEW;
+            Fragment fragment = PrintPreviewFragment.newInstance();
+            showFragment(fragment);
+        }
+    }
+
+    private void ensureErrorUiShown(CharSequence message, int action) {
+        if (mUiState != UI_STATE_ERROR) {
+            mUiState = UI_STATE_ERROR;
+            Fragment fragment = PrintErrorFragment.newInstance(message, action);
+            showFragment(fragment);
+        }
+    }
+
+    private void showFragment(Fragment fragment) {
+        FragmentTransaction transaction = getFragmentManager().beginTransaction();
+        Fragment oldFragment = getFragmentManager().findFragmentById(
+                R.id.embedded_content_container);
+        if (oldFragment != null) {
+            transaction.remove(oldFragment);
+        }
+        transaction.add(R.id.embedded_content_container, fragment);
+        transaction.commit();
+    }
+
+    private void requestCreatePdfFileOrFinish() {
+        if (getCurrentPrinter() == mDestinationSpinnerAdapter.getPdfPrinter()) {
+            startCreateDocumentActivity();
+        } else {
+            finish();
+        }
+    }
+
+    private void finishIfConfirmedOrCanceled() {
+        if (mState == STATE_PRINT_CONFIRMED) {
+            requestCreatePdfFileOrFinish();
+        } else if (mState == STATE_PRINT_CANCELED) {
+            finish();
+        }
+    }
+
+    private void updatePrintAttributesFromCapabilities(PrinterCapabilitiesInfo capabilities) {
+        PrintAttributes defaults = capabilities.getDefaults();
+
+        // Sort the media sizes based on the current locale.
+        List<MediaSize> sortedMediaSizes = new ArrayList<>(capabilities.getMediaSizes());
+        Collections.sort(sortedMediaSizes, mMediaSizeComparator);
+
+        PrintAttributes attributes = mPrintJob.getAttributes();
+
+        // Media size.
+        MediaSize currMediaSize = attributes.getMediaSize();
+        if (currMediaSize == null) {
+            attributes.setMediaSize(defaults.getMediaSize());
+        } else {
+            boolean foundCurrentMediaSize = false;
+            // Try to find the current media size in the capabilities as
+            // it may be in a different orientation.
+            MediaSize currMediaSizePortrait = currMediaSize.asPortrait();
+            final int mediaSizeCount = sortedMediaSizes.size();
+            for (int i = 0; i < mediaSizeCount; i++) {
+                MediaSize mediaSize = sortedMediaSizes.get(i);
+                if (currMediaSizePortrait.equals(mediaSize.asPortrait())) {
+                    attributes.setMediaSize(currMediaSize);
+                    foundCurrentMediaSize = true;
+                    break;
+                }
+            }
+            // If we did not find the current media size fall back to default.
+            if (!foundCurrentMediaSize) {
+                attributes.setMediaSize(defaults.getMediaSize());
+            }
+        }
+
+        // Color mode.
+        final int colorMode = attributes.getColorMode();
+        if ((capabilities.getColorModes() & colorMode) == 0) {
+            attributes.setColorMode(defaults.getColorMode());
+        }
+
+        // Resolution
+        Resolution resolution = attributes.getResolution();
+        if (resolution == null || !capabilities.getResolutions().contains(resolution)) {
+            attributes.setResolution(defaults.getResolution());
+        }
+
+        // Margins.
+        attributes.setMinMargins(defaults.getMinMargins());
+    }
+
+    private boolean updateDocument(boolean preview, boolean clearLastError) {
+        if (!clearLastError && mPrintedDocument.hasUpdateError()) {
+            return false;
+        }
+
+        if (clearLastError && mPrintedDocument.hasUpdateError()) {
+            mPrintedDocument.clearUpdateError();
+        }
+
+        if (mRequestedPages != null && mRequestedPages.length > 0) {
+            final PageRange[] pages;
+            if (preview) {
+                final int firstPage = mRequestedPages[0].getStart();
+                pages = new PageRange[]{new PageRange(firstPage, firstPage)};
+            } else {
+                pages = mRequestedPages;
+            }
+            final boolean willUpdate = mPrintedDocument.update(mPrintJob.getAttributes(),
+                    pages, preview);
+
+            if (willUpdate) {
+                mProgressMessageController.post();
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private void addCurrentPrinterToHistory() {
+        PrinterInfo currentPrinter = getCurrentPrinter();
+        if (currentPrinter != null) {
+            PrinterId fakePdfPrinterId = mDestinationSpinnerAdapter.getPdfPrinter().getId();
+            if (!currentPrinter.getId().equals(fakePdfPrinterId)) {
+                mPrinterRegistry.addHistoricalPrinter(currentPrinter);
+            }
+        }
+    }
+
+    private PrinterInfo getCurrentPrinter() {
+        return ((PrinterHolder) mDestinationSpinner.getSelectedItem()).printer;
+    }
+
+    private void cancelPrint() {
+        mState = STATE_PRINT_CANCELED;
+        updateOptionsUi();
+        if (mPrintedDocument.isUpdating()) {
+            mPrintedDocument.cancel();
+        }
+        finish();
+    }
+
+    private void confirmPrint() {
+        mState = STATE_PRINT_CONFIRMED;
+        updateOptionsUi();
+        if (canUpdateDocument()) {
+            updateDocument(false, false);
+        }
+        addCurrentPrinterToHistory();
+        if (!mPrintedDocument.isUpdating()) {
+            requestCreatePdfFileOrFinish();
+        }
+    }
+
+    private void bindUi() {
+        // Summary
+        mSummaryCopies = (TextView) findViewById(R.id.copies_count_summary);
+        mSummaryPaperSize = (TextView) findViewById(R.id.paper_size_summary);
+
+        // Options container
+        mOptionsContent = (ContentView) findViewById(R.id.options_content);
+        mOptionsContent.setOptionsStateChangeListener(new OptionsStateChangeListener() {
+            @Override
+            public void onOptionsOpened() {
+                // TODO: Update preview.
+            }
+
+            @Override
+            public void onOptionsClosed() {
+                // TODO: Update preview.
+            }
+        });
+
+        OnItemSelectedListener itemSelectedListener = new MyOnItemSelectedListener();
+        OnClickListener clickListener = new MyClickListener();
+
+        // Copies
+        mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
+        mCopiesEditText.setOnFocusChangeListener(mSelectAllOnFocusListener);
+        mCopiesEditText.setText(MIN_COPIES_STRING);
+        mCopiesEditText.setSelection(mCopiesEditText.getText().length());
+        mCopiesEditText.addTextChangedListener(new EditTextWatcher());
+
+        // Destination.
+        mDestinationSpinnerAdapter.registerDataSetObserver(new PrintersObserver());
+        mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
+        mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
+        mDestinationSpinner.setOnItemSelectedListener(itemSelectedListener);
+        mDestinationSpinner.setSelection(0);
+
+        // Media size.
+        mMediaSizeSpinnerAdapter = new ArrayAdapter<>(
+                this, R.layout.spinner_dropdown_item, R.id.title);
+        mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner);
+        mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
+        mMediaSizeSpinner.setOnItemSelectedListener(itemSelectedListener);
+
+        // Color mode.
+        mColorModeSpinnerAdapter = new ArrayAdapter<>(
+                this, R.layout.spinner_dropdown_item, R.id.title);
+        mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner);
+        mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
+        mColorModeSpinner.setOnItemSelectedListener(itemSelectedListener);
+
+        // Orientation
+        mOrientationSpinnerAdapter = new ArrayAdapter<>(
+                this, R.layout.spinner_dropdown_item, R.id.title);
+        String[] orientationLabels = getResources().getStringArray(
+              R.array.orientation_labels);
+        mOrientationSpinnerAdapter.add(new SpinnerItem<>(
+                ORIENTATION_PORTRAIT, orientationLabels[0]));
+        mOrientationSpinnerAdapter.add(new SpinnerItem<>(
+                ORIENTATION_LANDSCAPE, orientationLabels[1]));
+        mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
+        mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
+        mOrientationSpinner.setOnItemSelectedListener(itemSelectedListener);
+
+        // Range options
+        ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter =
+                new ArrayAdapter<>(this, R.layout.spinner_dropdown_item, R.id.title);
+        final int[] rangeOptionsValues = getResources().getIntArray(
+                R.array.page_options_values);
+        String[] rangeOptionsLabels = getResources().getStringArray(
+                R.array.page_options_labels);
+        final int rangeOptionsCount = rangeOptionsLabels.length;
+        for (int i = 0; i < rangeOptionsCount; i++) {
+            rangeOptionsSpinnerAdapter.add(new SpinnerItem<>(
+                    rangeOptionsValues[i], rangeOptionsLabels[i]));
+        }
+        mPageRangeOptionsTitle = (TextView) findViewById(R.id.range_options_title);
+        mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner);
+        mRangeOptionsSpinner.setAdapter(rangeOptionsSpinnerAdapter);
+        mRangeOptionsSpinner.setOnItemSelectedListener(itemSelectedListener);
+
+        // Page range
+        mPageRangeTitle = (TextView) findViewById(R.id.page_range_title);
+        mPageRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
+        mPageRangeEditText.setOnFocusChangeListener(mSelectAllOnFocusListener);
+        mPageRangeEditText.addTextChangedListener(new RangeTextWatcher());
+
+        // Advanced options button.
+        mAdvancedPrintOptionsContainer = findViewById(R.id.more_options_container);
+        mMoreOptionsButton = (Button) findViewById(R.id.more_options_button);
+        mMoreOptionsButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                PrinterInfo currentPrinter = getCurrentPrinter();
+                if (currentPrinter != null) {
+                    startAdvancedPrintOptionsActivity(currentPrinter);
+                }
+            }
+        });
+
+        // Print button
+        mPrintButton = (ImageView) findViewById(R.id.print_button);
+        mPrintButton.setOnClickListener(clickListener);
+    }
+
+    private final class MyClickListener implements OnClickListener {
+        @Override
+        public void onClick(View view) {
+            if (view == mPrintButton) {
+                PrinterInfo currentPrinter = getCurrentPrinter();
+                if (currentPrinter != null) {
+                    confirmPrint();
+                } else {
+                    cancelPrint();
+                }
+            } else if (view == mMoreOptionsButton) {
+                PrinterInfo currentPrinter = getCurrentPrinter();
+                if (currentPrinter != null) {
+                    startAdvancedPrintOptionsActivity(currentPrinter);
+                }
+            }
+        }
+    }
+
+    private static boolean canPrint(PrinterInfo printer) {
+        return printer.getCapabilities() != null
+                && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
+    }
+
+    private void updateOptionsUi() {
+        // Always update the summary.
+        if (!TextUtils.isEmpty(mCopiesEditText.getText())) {
+            mSummaryCopies.setText(mCopiesEditText.getText());
+        }
+
+        final int selectedMediaIndex = mMediaSizeSpinner.getSelectedItemPosition();
+        if (selectedMediaIndex >= 0) {
+            SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(selectedMediaIndex);
+            mSummaryPaperSize.setText(mediaItem.label);
+        }
+
+        if (mState == STATE_PRINT_CONFIRMED
+                || mState == STATE_PRINT_CANCELED
+                || mState == STATE_UPDATE_FAILED
+                || mState == STATE_CREATE_FILE_FAILED
+                || mState == STATE_PRINTER_UNAVAILABLE) {
+            if (mState != STATE_PRINTER_UNAVAILABLE) {
+                mDestinationSpinner.setEnabled(false);
+            }
+            mCopiesEditText.setEnabled(false);
+            mMediaSizeSpinner.setEnabled(false);
+            mColorModeSpinner.setEnabled(false);
+            mOrientationSpinner.setEnabled(false);
+            mRangeOptionsSpinner.setEnabled(false);
+            mPageRangeEditText.setEnabled(false);
+            mPrintButton.setEnabled(false);
+            mMoreOptionsButton.setEnabled(false);
+            return;
+        }
+
+        // If no current printer, or it has no capabilities, or it is not
+        // available, we disable all print options except the destination.
+        PrinterInfo currentPrinter =  getCurrentPrinter();
+        if (currentPrinter == null || !canPrint(currentPrinter)) {
+            mCopiesEditText.setEnabled(false);
+            mMediaSizeSpinner.setEnabled(false);
+            mColorModeSpinner.setEnabled(false);
+            mOrientationSpinner.setEnabled(false);
+            mRangeOptionsSpinner.setEnabled(false);
+            mPageRangeEditText.setEnabled(false);
+            mPrintButton.setEnabled(false);
+            mMoreOptionsButton.setEnabled(false);
+            return;
+        }
+
+        PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
+        PrintAttributes defaultAttributes = capabilities.getDefaults();
+
+        // Media size.
+        mMediaSizeSpinner.setEnabled(true);
+
+        List<MediaSize> mediaSizes = new ArrayList<>(capabilities.getMediaSizes());
+        // Sort the media sizes based on the current locale.
+        Collections.sort(mediaSizes, mMediaSizeComparator);
+
+        PrintAttributes attributes = mPrintJob.getAttributes();
+
+        // If the media sizes changed, we update the adapter and the spinner.
+        boolean mediaSizesChanged = false;
+        final int mediaSizeCount = mediaSizes.size();
+        if (mediaSizeCount != mMediaSizeSpinnerAdapter.getCount()) {
+            mediaSizesChanged = true;
+        } else {
+            for (int i = 0; i < mediaSizeCount; i++) {
+                if (!mediaSizes.get(i).equals(mMediaSizeSpinnerAdapter.getItem(i).value)) {
+                    mediaSizesChanged = true;
+                    break;
+                }
+            }
+        }
+        if (mediaSizesChanged) {
+            // Remember the old media size to try selecting it again.
+            int oldMediaSizeNewIndex = AdapterView.INVALID_POSITION;
+            MediaSize oldMediaSize = attributes.getMediaSize();
+
+            // Rebuild the adapter data.
+            mMediaSizeSpinnerAdapter.clear();
+            for (int i = 0; i < mediaSizeCount; i++) {
+                MediaSize mediaSize = mediaSizes.get(i);
+                if (oldMediaSize != null
+                        && mediaSize.asPortrait().equals(oldMediaSize.asPortrait())) {
+                    // Update the index of the old selection.
+                    oldMediaSizeNewIndex = i;
+                }
+                mMediaSizeSpinnerAdapter.add(new SpinnerItem<>(
+                        mediaSize, mediaSize.getLabel(getPackageManager())));
+            }
+
+            if (oldMediaSizeNewIndex != AdapterView.INVALID_POSITION) {
+                // Select the old media size - nothing really changed.
+                if (mMediaSizeSpinner.getSelectedItemPosition() != oldMediaSizeNewIndex) {
+                    mMediaSizeSpinner.setSelection(oldMediaSizeNewIndex);
+                }
+            } else {
+                // Select the first or the default.
+                final int mediaSizeIndex = Math.max(mediaSizes.indexOf(
+                        defaultAttributes.getMediaSize()), 0);
+                if (mMediaSizeSpinner.getSelectedItemPosition() != mediaSizeIndex) {
+                    mMediaSizeSpinner.setSelection(mediaSizeIndex);
+                }
+                // Respect the orientation of the old selection.
+                if (oldMediaSize != null) {
+                    if (oldMediaSize.isPortrait()) {
+                        attributes.setMediaSize(mMediaSizeSpinnerAdapter
+                                .getItem(mediaSizeIndex).value.asPortrait());
+                    } else {
+                        attributes.setMediaSize(mMediaSizeSpinnerAdapter
+                                .getItem(mediaSizeIndex).value.asLandscape());
+                    }
+                }
+            }
+        }
+
+        // Color mode.
+        mColorModeSpinner.setEnabled(true);
+        final int colorModes = capabilities.getColorModes();
+
+        // If the color modes changed, we update the adapter and the spinner.
+        boolean colorModesChanged = false;
+        if (Integer.bitCount(colorModes) != mColorModeSpinnerAdapter.getCount()) {
+            colorModesChanged = true;
+        } else {
+            int remainingColorModes = colorModes;
+            int adapterIndex = 0;
+            while (remainingColorModes != 0) {
+                final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes);
+                final int colorMode = 1 << colorBitOffset;
+                remainingColorModes &= ~colorMode;
+                if (colorMode != mColorModeSpinnerAdapter.getItem(adapterIndex).value) {
+                    colorModesChanged = true;
+                    break;
+                }
+                adapterIndex++;
+            }
+        }
+        if (colorModesChanged) {
+            // Remember the old color mode to try selecting it again.
+            int oldColorModeNewIndex = AdapterView.INVALID_POSITION;
+            final int oldColorMode = attributes.getColorMode();
+
+            // Rebuild the adapter data.
+            mColorModeSpinnerAdapter.clear();
+            String[] colorModeLabels = getResources().getStringArray(R.array.color_mode_labels);
+            int remainingColorModes = colorModes;
+            while (remainingColorModes != 0) {
+                final int colorBitOffset = Integer.numberOfTrailingZeros(remainingColorModes);
+                final int colorMode = 1 << colorBitOffset;
+                if (colorMode == oldColorMode) {
+                    // Update the index of the old selection.
+                    oldColorModeNewIndex = colorBitOffset;
+                }
+                remainingColorModes &= ~colorMode;
+                mColorModeSpinnerAdapter.add(new SpinnerItem<>(colorMode,
+                        colorModeLabels[colorBitOffset]));
+            }
+            if (oldColorModeNewIndex != AdapterView.INVALID_POSITION) {
+                // Select the old color mode - nothing really changed.
+                if (mColorModeSpinner.getSelectedItemPosition() != oldColorModeNewIndex) {
+                    mColorModeSpinner.setSelection(oldColorModeNewIndex);
+                }
+            } else {
+                // Select the default.
+                final int selectedColorMode = colorModes & defaultAttributes.getColorMode();
+                final int itemCount = mColorModeSpinnerAdapter.getCount();
+                for (int i = 0; i < itemCount; i++) {
+                    SpinnerItem<Integer> item = mColorModeSpinnerAdapter.getItem(i);
+                    if (selectedColorMode == item.value) {
+                        if (mColorModeSpinner.getSelectedItemPosition() != i) {
+                            mColorModeSpinner.setSelection(i);
+                        }
+                        attributes.setColorMode(selectedColorMode);
+                    }
+                }
+            }
+        }
+
+        // Orientation
+        mOrientationSpinner.setEnabled(true);
+        MediaSize mediaSize = attributes.getMediaSize();
+        if (mediaSize != null) {
+            if (mediaSize.isPortrait()
+                    && mOrientationSpinner.getSelectedItemPosition() != 0) {
+                mOrientationSpinner.setSelection(0);
+            } else if (!mediaSize.isPortrait()
+                    && mOrientationSpinner.getSelectedItemPosition() != 1) {
+                mOrientationSpinner.setSelection(1);
+            }
+        }
+
+        // Range options
+        PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
+        if (info != null && info.getPageCount() > 0) {
+            if (info.getPageCount() == 1) {
+                mRangeOptionsSpinner.setEnabled(false);
+            } else {
+                mRangeOptionsSpinner.setEnabled(true);
+                if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
+                    if (!mPageRangeEditText.isEnabled()) {
+                        mPageRangeEditText.setEnabled(true);
+                        mPageRangeEditText.setVisibility(View.VISIBLE);
+                        mPageRangeTitle.setVisibility(View.VISIBLE);
+                        mPageRangeEditText.requestFocus();
+                        InputMethodManager imm = (InputMethodManager)
+                                getSystemService(Context.INPUT_METHOD_SERVICE);
+                        imm.showSoftInput(mPageRangeEditText, 0);
+                    }
+                } else {
+                    mPageRangeEditText.setEnabled(false);
+                    mPageRangeEditText.setVisibility(View.INVISIBLE);
+                    mPageRangeTitle.setVisibility(View.INVISIBLE);
+                }
+            }
+            String title = (info.getPageCount() != PrintDocumentInfo.PAGE_COUNT_UNKNOWN)
+                    ? getString(R.string.label_pages, String.valueOf(info.getPageCount()))
+                    : getString(R.string.page_count_unknown);
+            mPageRangeOptionsTitle.setText(title);
+        } else {
+            if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
+                mRangeOptionsSpinner.setSelection(0);
+            }
+            mRangeOptionsSpinner.setEnabled(false);
+            mPageRangeOptionsTitle.setText(getString(R.string.page_count_unknown));
+            mPageRangeEditText.setEnabled(false);
+            mPageRangeEditText.setVisibility(View.INVISIBLE);
+            mPageRangeTitle.setVisibility(View.INVISIBLE);
+        }
+
+        // Advanced print options
+        ComponentName serviceName = currentPrinter.getId().getServiceName();
+        if (!TextUtils.isEmpty(PrintOptionUtils.getAdvancedOptionsActivityName(
+                this, serviceName))) {
+            mAdvancedPrintOptionsContainer.setVisibility(View.VISIBLE);
+            mMoreOptionsButton.setEnabled(true);
+        } else {
+            mAdvancedPrintOptionsContainer.setVisibility(View.GONE);
+            mMoreOptionsButton.setEnabled(false);
+        }
+
+        // Print
+        if (mDestinationSpinnerAdapter.getPdfPrinter() != currentPrinter) {
+            mPrintButton.setImageResource(com.android.internal.R.drawable.ic_print);
+        } else {
+            mPrintButton.setImageResource(com.android.internal.R.drawable.ic_menu_save);
+        }
+        if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1
+                && (TextUtils.isEmpty(mPageRangeEditText.getText()) || hasErrors()))
+            || (mRangeOptionsSpinner.getSelectedItemPosition() == 0
+                && (mPrintedDocument.getDocumentInfo() == null || hasErrors()))) {
+            mPrintButton.setEnabled(false);
+        } else {
+            mPrintButton.setEnabled(true);
+        }
+
+        // Copies
+        if (mDestinationSpinnerAdapter.getPdfPrinter() != currentPrinter) {
+            mCopiesEditText.setEnabled(true);
+        } else {
+            mCopiesEditText.setEnabled(false);
+        }
+        if (mCopiesEditText.getError() == null
+                && TextUtils.isEmpty(mCopiesEditText.getText())) {
+            mCopiesEditText.setText(String.valueOf(MIN_COPIES));
+            mCopiesEditText.requestFocus();
+        }
+    }
+
+    private PageRange[] computeRequestedPages() {
+        if (hasErrors()) {
+            return null;
+        }
+
+        if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
+            List<PageRange> pageRanges = new ArrayList<>();
+            mStringCommaSplitter.setString(mPageRangeEditText.getText().toString());
+
+            while (mStringCommaSplitter.hasNext()) {
+                String range = mStringCommaSplitter.next().trim();
+                if (TextUtils.isEmpty(range)) {
+                    continue;
+                }
+                final int dashIndex = range.indexOf('-');
+                final int fromIndex;
+                final int toIndex;
+
+                if (dashIndex > 0) {
+                    fromIndex = Integer.parseInt(range.substring(0, dashIndex).trim()) - 1;
+                    // It is possible that the dash is at the end since the input
+                    // verification can has to allow the user to keep entering if
+                    // this would lead to a valid input. So we handle this.
+                    if (dashIndex < range.length() - 1) {
+                        String fromString = range.substring(dashIndex + 1, range.length()).trim();
+                        toIndex = Integer.parseInt(fromString) - 1;
+                    } else {
+                        toIndex = fromIndex;
+                    }
+                } else {
+                    fromIndex = toIndex = Integer.parseInt(range) - 1;
+                }
+
+                PageRange pageRange = new PageRange(Math.min(fromIndex, toIndex),
+                        Math.max(fromIndex, toIndex));
+                pageRanges.add(pageRange);
+            }
+
+            PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
+            pageRanges.toArray(pageRangesArray);
+
+            return PageRangeUtils.normalize(pageRangesArray);
+        }
+
+        return ALL_PAGES_ARRAY;
+    }
+
+    private boolean hasErrors() {
+        return (mCopiesEditText.getError() != null)
+                || (mPageRangeEditText.getVisibility() == View.VISIBLE
+                    && mPageRangeEditText.getError() != null);
+    }
+
+    public void onPrinterAvailable(PrinterInfo printer) {
+        PrinterInfo currentPrinter = getCurrentPrinter();
+        if (currentPrinter.equals(printer)) {
+            mState = STATE_CONFIGURING;
+            if (canUpdateDocument()) {
+                updateDocument(true, false);
+            }
+            ensurePreviewUiShown();
+            updateOptionsUi();
+        }
+    }
+
+    public void onPrinterUnavailable(PrinterInfo printer) {
+        if (getCurrentPrinter().getId().equals(printer.getId())) {
+            mState = STATE_PRINTER_UNAVAILABLE;
+            if (mPrintedDocument.isUpdating()) {
+                mPrintedDocument.cancel();
+            }
+            ensureErrorUiShown(getString(R.string.print_error_printer_unavailable),
+                    PrintErrorFragment.ACTION_NONE);
+            updateOptionsUi();
+        }
+    }
+
+    private final class SpinnerItem<T> {
+        final T value;
+        final CharSequence label;
+
+        public SpinnerItem(T value, CharSequence label) {
+            this.value = value;
+            this.label = label;
+        }
+
+        public String toString() {
+            return label.toString();
+        }
+    }
+
+    private final class PrinterAvailabilityDetector implements Runnable {
+        private static final long UNAVAILABLE_TIMEOUT_MILLIS = 10000; // 10sec
+
+        private boolean mPosted;
+
+        private boolean mPrinterUnavailable;
+
+        private PrinterInfo mPrinter;
+
+        public void updatePrinter(PrinterInfo printer) {
+            if (printer.equals(mDestinationSpinnerAdapter.getPdfPrinter())) {
+                return;
+            }
+
+            final boolean available = printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
+                    && printer.getCapabilities() != null;
+            final boolean notifyIfAvailable;
+
+            if (mPrinter == null || !mPrinter.getId().equals(printer.getId())) {
+                notifyIfAvailable = true;
+                unpostIfNeeded();
+                mPrinterUnavailable = false;
+                mPrinter = new PrinterInfo.Builder(printer).build();
+            } else {
+                notifyIfAvailable =
+                     (mPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE
+                        && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE)
+                    || (mPrinter.getCapabilities() == null
+                        && printer.getCapabilities() != null);
+                mPrinter.copyFrom(printer);
+            }
+
+            if (available) {
+                unpostIfNeeded();
+                mPrinterUnavailable = false;
+                if (notifyIfAvailable) {
+                    onPrinterAvailable(mPrinter);
+                }
+            } else {
+                if (!mPrinterUnavailable) {
+                    postIfNeeded();
+                }
+            }
+        }
+
+        public void cancel() {
+            unpostIfNeeded();
+            mPrinterUnavailable = false;
+        }
+
+        private void postIfNeeded() {
+            if (!mPosted) {
+                mPosted = true;
+                mDestinationSpinner.postDelayed(this, UNAVAILABLE_TIMEOUT_MILLIS);
+            }
+        }
+
+        private void unpostIfNeeded() {
+            if (mPosted) {
+                mPosted = false;
+                mDestinationSpinner.removeCallbacks(this);
+            }
+        }
+
+        @Override
+        public void run() {
+            mPosted = false;
+            mPrinterUnavailable = true;
+            onPrinterUnavailable(mPrinter);
+        }
+    }
+
+    private static final class PrinterHolder {
+        PrinterInfo printer;
+        boolean removed;
+
+        public PrinterHolder(PrinterInfo printer) {
+            this.printer = printer;
+        }
+    }
+
+    private final class DestinationAdapter extends BaseAdapter
+            implements PrinterRegistry.OnPrintersChangeListener {
+        private final List<PrinterHolder> mPrinterHolders = new ArrayList<>();
+
+        private final PrinterHolder mFakePdfPrinterHolder;
+
+        public DestinationAdapter() {
+            addPrinters(mPrinterHolders, mPrinterRegistry.getPrinters());
+            mPrinterRegistry.setOnPrintersChangeListener(this);
+            mFakePdfPrinterHolder = new PrinterHolder(createFakePdfPrinter());
+        }
+
+        public PrinterInfo getPdfPrinter() {
+            return mFakePdfPrinterHolder.printer;
+        }
+
+        public int getPrinterIndex(PrinterId printerId) {
+            for (int i = 0; i < getCount(); i++) {
+                PrinterHolder printerHolder = (PrinterHolder) getItem(i);
+                if (printerHolder != null && !printerHolder.removed
+                        && printerHolder.printer.getId().equals(printerId)) {
+                    return i;
+                }
+            }
+            return AdapterView.INVALID_POSITION;
+        }
+
+        public void ensurePrinterInVisibleAdapterPosition(PrinterId printerId) {
+            final int printerCount = mPrinterHolders.size();
+            for (int i = 0; i < printerCount; i++) {
+                PrinterHolder printerHolder = mPrinterHolders.get(i);
+                if (printerHolder.printer.getId().equals(printerId)) {
+                    // If already in the list - do nothing.
+                    if (i < getCount() - 2) {
+                        return;
+                    }
+                    // Else replace the last one (two items are not printers).
+                    final int lastPrinterIndex = getCount() - 3;
+                    mPrinterHolders.set(i, mPrinterHolders.get(lastPrinterIndex));
+                    mPrinterHolders.set(lastPrinterIndex, printerHolder);
+                    notifyDataSetChanged();
+                    return;
+                }
+            }
+        }
+
+        @Override
+        public int getCount() {
+            return Math.min(mPrinterHolders.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT);
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            Object item = getItem(position);
+            if (item instanceof PrinterHolder) {
+                PrinterHolder printerHolder = (PrinterHolder) item;
+                return !printerHolder.removed
+                        && printerHolder.printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
+            }
+            return true;
+        }
+
+        @Override
+        public Object getItem(int position) {
+            if (mPrinterHolders.isEmpty()) {
+                if (position == 0) {
+                    return mFakePdfPrinterHolder;
+                }
+            } else {
+                if (position < 1) {
+                    return mPrinterHolders.get(position);
+                }
+                if (position == 1) {
+                    return mFakePdfPrinterHolder;
+                }
+                if (position < getCount() - 1) {
+                    return mPrinterHolders.get(position - 1);
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            if (mPrinterHolders.isEmpty()) {
+                if (position == 0) {
+                    return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
+                } else if (position == 1) {
+                    return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
+                }
+            } else {
+                if (position == 1) {
+                    return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
+                }
+                if (position == getCount() - 1) {
+                    return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
+                }
+            }
+            return position;
+        }
+
+        @Override
+        public View getDropDownView(int position, View convertView, ViewGroup parent) {
+            View view = getView(position, convertView, parent);
+            view.setEnabled(isEnabled(position));
+            return view;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = getLayoutInflater().inflate(
+                        R.layout.printer_dropdown_item, parent, false);
+            }
+
+            CharSequence title = null;
+            CharSequence subtitle = null;
+            Drawable icon = null;
+
+            if (mPrinterHolders.isEmpty()) {
+                if (position == 0 && getPdfPrinter() != null) {
+                    PrinterHolder printerHolder = (PrinterHolder) getItem(position);
+                    title = printerHolder.printer.getName();
+                    icon = getResources().getDrawable(com.android.internal.R.drawable.ic_menu_save);
+                } else if (position == 1) {
+                    title = getString(R.string.all_printers);
+                }
+            } else {
+                if (position == 1 && getPdfPrinter() != null) {
+                    PrinterHolder printerHolder = (PrinterHolder) getItem(position);
+                    title = printerHolder.printer.getName();
+                    icon = getResources().getDrawable(com.android.internal.R.drawable.ic_menu_save);
+                } else if (position == getCount() - 1) {
+                    title = getString(R.string.all_printers);
+                } else {
+                    PrinterHolder printerHolder = (PrinterHolder) getItem(position);
+                    title = printerHolder.printer.getName();
+                    try {
+                        PackageInfo packageInfo = getPackageManager().getPackageInfo(
+                                printerHolder.printer.getId().getServiceName().getPackageName(), 0);
+                        subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager());
+                        icon = packageInfo.applicationInfo.loadIcon(getPackageManager());
+                    } catch (NameNotFoundException nnfe) {
+                        /* ignore */
+                    }
+                }
+            }
+
+            TextView titleView = (TextView) convertView.findViewById(R.id.title);
+            titleView.setText(title);
+
+            TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
+            if (!TextUtils.isEmpty(subtitle)) {
+                subtitleView.setText(subtitle);
+                subtitleView.setVisibility(View.VISIBLE);
+            } else {
+                subtitleView.setText(null);
+                subtitleView.setVisibility(View.GONE);
+            }
+
+            ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
+            if (icon != null) {
+                iconView.setImageDrawable(icon);
+                iconView.setVisibility(View.VISIBLE);
+            } else {
+                iconView.setVisibility(View.INVISIBLE);
+            }
+
+            return convertView;
+        }
+
+        @Override
+        public void onPrintersChanged(List<PrinterInfo> printers) {
+            // We rearrange the printers if the user selects a printer
+            // not shown in the initial short list. Therefore, we have
+            // to keep the printer order.
+
+            // No old printers - do not bother keeping their position.
+            if (mPrinterHolders.isEmpty()) {
+                addPrinters(mPrinterHolders, printers);
+                notifyDataSetChanged();
+                return;
+            }
+
+            // Add the new printers to a map.
+            ArrayMap<PrinterId, PrinterInfo> newPrintersMap = new ArrayMap<>();
+            final int printerCount = printers.size();
+            for (int i = 0; i < printerCount; i++) {
+                PrinterInfo printer = printers.get(i);
+                newPrintersMap.put(printer.getId(), printer);
+            }
+
+            List<PrinterHolder> newPrinterHolders = new ArrayList<>();
+
+            // Update printers we already have which are either updated or removed.
+            // We do not remove printers if the currently selected printer is removed
+            // to prevent the user printing to a wrong printer.
+            final int oldPrinterCount = mPrinterHolders.size();
+            for (int i = 0; i < oldPrinterCount; i++) {
+                PrinterHolder printerHolder = mPrinterHolders.get(i);
+                PrinterId oldPrinterId = printerHolder.printer.getId();
+                PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId);
+                if (updatedPrinter != null) {
+                    printerHolder.printer = updatedPrinter;
+                } else {
+                    printerHolder.removed = true;
+                }
+                newPrinterHolders.add(printerHolder);
+            }
+
+            // Add the rest of the new printers, i.e. what is left.
+            addPrinters(newPrinterHolders, newPrintersMap.values());
+
+            mPrinterHolders.clear();
+            mPrinterHolders.addAll(newPrinterHolders);
+
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public void onPrintersInvalid() {
+            mPrinterHolders.clear();
+            notifyDataSetInvalidated();
+        }
+
+        public PrinterHolder getPrinterHolder(PrinterId printerId) {
+            final int itemCount = getCount();
+            for (int i = 0; i < itemCount; i++) {
+                Object item = getItem(i);
+                if (item instanceof PrinterHolder) {
+                    PrinterHolder printerHolder = (PrinterHolder) item;
+                    if (printerId.equals(printerHolder.printer.getId())) {
+                        return printerHolder;
+                    }
+                }
+            }
+            return null;
+        }
+
+        public void pruneRemovedPrinters() {
+            final int holderCounts = mPrinterHolders.size();
+            for (int i = holderCounts - 1; i >= 0; i--) {
+                PrinterHolder printerHolder = mPrinterHolders.get(i);
+                if (printerHolder.removed) {
+                    mPrinterHolders.remove(i);
+                }
+            }
+        }
+
+        private void addPrinters(List<PrinterHolder> list, Collection<PrinterInfo> printers) {
+            for (PrinterInfo printer : printers) {
+                PrinterHolder printerHolder = new PrinterHolder(printer);
+                list.add(printerHolder);
+            }
+        }
+
+        private PrinterInfo createFakePdfPrinter() {
+            MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintActivity.this);
+
+            PrinterId printerId = new PrinterId(getComponentName(), "PDF printer");
+
+            PrinterCapabilitiesInfo.Builder builder =
+                    new PrinterCapabilitiesInfo.Builder(printerId);
+
+            String[] mediaSizeIds = getResources().getStringArray(R.array.pdf_printer_media_sizes);
+            final int mediaSizeIdCount = mediaSizeIds.length;
+            for (int i = 0; i < mediaSizeIdCount; i++) {
+                String id = mediaSizeIds[i];
+                MediaSize mediaSize = MediaSize.getStandardMediaSizeById(id);
+                builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize));
+            }
+
+            builder.addResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300),
+                    true);
+            builder.setColorModes(PrintAttributes.COLOR_MODE_COLOR
+                    | PrintAttributes.COLOR_MODE_MONOCHROME, PrintAttributes.COLOR_MODE_COLOR);
+
+            return new PrinterInfo.Builder(printerId, getString(R.string.save_as_pdf),
+                    PrinterInfo.STATUS_IDLE).setCapabilities(builder.build()).build();
+        }
+    }
+
+    private final class PrintersObserver extends DataSetObserver {
+        @Override
+        public void onChanged() {
+            PrinterInfo oldPrinterState = mOldCurrentPrinter;
+            if (oldPrinterState == null) {
+                return;
+            }
+
+            PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
+                    oldPrinterState.getId());
+            if (printerHolder == null) {
+                return;
+            }
+            PrinterInfo newPrinterState = printerHolder.printer;
+
+            if (!printerHolder.removed) {
+                mDestinationSpinnerAdapter.pruneRemovedPrinters();
+            } else {
+                onPrinterUnavailable(newPrinterState);
+            }
+
+            if (oldPrinterState.equals(newPrinterState)) {
+                return;
+            }
+
+            PrinterCapabilitiesInfo oldCapab = oldPrinterState.getCapabilities();
+            PrinterCapabilitiesInfo newCapab = newPrinterState.getCapabilities();
+
+            final boolean hasCapab = newCapab != null;
+            final boolean gotCapab = oldCapab == null && newCapab != null;
+            final boolean lostCapab = oldCapab != null && newCapab == null;
+            final boolean capabChanged = capabilitiesChanged(oldCapab, newCapab);
+
+            final int oldStatus = oldPrinterState.getStatus();
+            final int newStatus = newPrinterState.getStatus();
+
+            final boolean isActive = newStatus != PrinterInfo.STATUS_UNAVAILABLE;
+            final boolean becameActive = (oldStatus == PrinterInfo.STATUS_UNAVAILABLE
+                    && oldStatus != newStatus);
+            final boolean becameInactive = (newStatus == PrinterInfo.STATUS_UNAVAILABLE
+                    && oldStatus != newStatus);
+
+            mPrinterAvailabilityDetector.updatePrinter(newPrinterState);
+
+            oldPrinterState.copyFrom(newPrinterState);
+
+            if ((isActive && gotCapab) || (becameActive && hasCapab)) {
+                onPrinterAvailable(newPrinterState);
+            } else if ((becameInactive && hasCapab)|| (isActive && lostCapab)) {
+                onPrinterUnavailable(newPrinterState);
+            }
+
+            if (hasCapab && capabChanged) {
+                updatePrintAttributesFromCapabilities(newCapab);
+            }
+
+            final boolean updateNeeded = ((capabChanged && hasCapab && isActive)
+                    || (becameActive && hasCapab) || (isActive && gotCapab));
+
+            if (updateNeeded && canUpdateDocument()) {
+                updateDocument(true, false);
+            }
+
+            updateOptionsUi();
+        }
+
+        private boolean capabilitiesChanged(PrinterCapabilitiesInfo oldCapabilities,
+                PrinterCapabilitiesInfo newCapabilities) {
+            if (oldCapabilities == null) {
+                if (newCapabilities != null) {
+                    return true;
+                }
+            } else if (!oldCapabilities.equals(newCapabilities)) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private final class MyOnItemSelectedListener implements AdapterView.OnItemSelectedListener {
+        @Override
+        public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
+            if (spinner == mDestinationSpinner) {
+                if (position == AdapterView.INVALID_POSITION) {
+                    return;
+                }
+
+                if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) {
+                    startSelectPrinterActivity();
+                    return;
+                }
+
+                PrinterInfo currentPrinter = getCurrentPrinter();
+
+                // Why on earth item selected is called if no selection changed.
+                if (mOldCurrentPrinter == currentPrinter) {
+                    return;
+                }
+                mOldCurrentPrinter = currentPrinter;
+
+                PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
+                        currentPrinter.getId());
+                if (!printerHolder.removed) {
+                    mDestinationSpinnerAdapter.pruneRemovedPrinters();
+                    ensurePreviewUiShown();
+                }
+
+                mPrintJob.setPrinterId(currentPrinter.getId());
+                mPrintJob.setPrinterName(currentPrinter.getName());
+
+                mPrinterRegistry.setTrackedPrinter(currentPrinter.getId());
+
+                PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
+                if (capabilities != null) {
+                   updatePrintAttributesFromCapabilities(capabilities);
+                }
+
+                mPrinterAvailabilityDetector.updatePrinter(currentPrinter);
+            } else if (spinner == mMediaSizeSpinner) {
+                SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
+                if (mOrientationSpinner.getSelectedItemPosition() == 0) {
+                    mPrintJob.getAttributes().setMediaSize(mediaItem.value.asPortrait());
+                } else {
+                    mPrintJob.getAttributes().setMediaSize(mediaItem.value.asLandscape());
+                }
+            } else if (spinner == mColorModeSpinner) {
+                SpinnerItem<Integer> colorModeItem = mColorModeSpinnerAdapter.getItem(position);
+                mPrintJob.getAttributes().setColorMode(colorModeItem.value);
+            } else if (spinner == mOrientationSpinner) {
+                SpinnerItem<Integer> orientationItem = mOrientationSpinnerAdapter.getItem(position);
+                PrintAttributes attributes = mPrintJob.getAttributes();
+                if (mMediaSizeSpinner.getSelectedItem() != null) {
+                    if (orientationItem.value == ORIENTATION_PORTRAIT) {
+                        attributes.copyFrom(attributes.asPortrait());
+                    } else {
+                        attributes.copyFrom(attributes.asLandscape());
+                    }
+                }
+            }
+
+            if (canUpdateDocument()) {
+                updateDocument(true, false);
+            }
+
+            updateOptionsUi();
+        }
+
+        @Override
+        public void onNothingSelected(AdapterView<?> parent) {
+            /* do nothing*/
+        }
+    }
+
+    private boolean canUpdateDocument() {
+        if (mPrintedDocument.isDestroyed()) {
+            return false;
+        }
+
+        if (hasErrors()) {
+            return false;
+        }
+
+        PrintAttributes attributes = mPrintJob.getAttributes();
+
+        final int colorMode = attributes.getColorMode();
+        if (colorMode != PrintAttributes.COLOR_MODE_COLOR
+                && colorMode != PrintAttributes.COLOR_MODE_MONOCHROME) {
+            return false;
+        }
+        if (attributes.getMediaSize() == null) {
+            return false;
+        }
+        if (attributes.getMinMargins() == null) {
+            return false;
+        }
+        if (attributes.getResolution() == null) {
+            return false;
+        }
+
+        PrinterInfo currentPrinter = getCurrentPrinter();
+        if (currentPrinter == null) {
+            return false;
+        }
+        PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
+        if (capabilities == null) {
+            return false;
+        }
+        if (currentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private final class SelectAllOnFocusListener implements OnFocusChangeListener {
+        @Override
+        public void onFocusChange(View view, boolean hasFocus) {
+            EditText editText = (EditText) view;
+            if (!TextUtils.isEmpty(editText.getText())) {
+                editText.setSelection(editText.getText().length());
+            }
+        }
+    }
+
+    private final class RangeTextWatcher implements TextWatcher {
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+            /* do nothing */
+        }
+
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            /* do nothing */
+        }
+
+        @Override
+        public void afterTextChanged(Editable editable) {
+            final boolean hadErrors = hasErrors();
+
+            String text = editable.toString();
+
+            if (TextUtils.isEmpty(text)) {
+                mPageRangeEditText.setError("");
+                updateOptionsUi();
+                return;
+            }
+
+            String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////");
+            if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) {
+                mPageRangeEditText.setError("");
+                updateOptionsUi();
+                return;
+            }
+
+            PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
+            final int pageCount = (info != null) ? info.getPageCount() : 0;
+
+            // The range
+            Matcher matcher = PATTERN_DIGITS.matcher(text);
+            while (matcher.find()) {
+                String numericString = text.substring(matcher.start(), matcher.end()).trim();
+                if (TextUtils.isEmpty(numericString)) {
+                    continue;
+                }
+                final int pageIndex = Integer.parseInt(numericString);
+                if (pageIndex < 1 || pageIndex > pageCount) {
+                    mPageRangeEditText.setError("");
+                    updateOptionsUi();
+                    return;
+                }
+            }
+
+            // We intentionally do not catch the case of the from page being
+            // greater than the to page. When computing the requested pages
+            // we just swap them if necessary.
+
+            // Keep the print job up to date with the selected pages if we
+            // know how many pages are there in the document.
+            mRequestedPages = computeRequestedPages();
+
+            mPageRangeEditText.setError(null);
+            mPrintButton.setEnabled(true);
+            updateOptionsUi();
+
+            if (hadErrors && !hasErrors()) {
+                updateOptionsUi();
+            }
+        }
+    }
+
+    private final class EditTextWatcher implements TextWatcher {
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+            /* do nothing */
+        }
+
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            /* do nothing */
+        }
+
+        @Override
+        public void afterTextChanged(Editable editable) {
+            final boolean hadErrors = hasErrors();
+
+            if (editable.length() == 0) {
+                mCopiesEditText.setError("");
+                updateOptionsUi();
+                return;
+            }
+
+            int copies = 0;
+            try {
+                copies = Integer.parseInt(editable.toString());
+            } catch (NumberFormatException nfe) {
+                /* ignore */
+            }
+
+            if (copies < MIN_COPIES) {
+                mCopiesEditText.setError("");
+                updateOptionsUi();
+                return;
+            }
+
+            mPrintJob.setCopies(copies);
+
+            mCopiesEditText.setError(null);
+
+            updateOptionsUi();
+
+            if (hadErrors && canUpdateDocument()) {
+                updateDocument(true, false);
+            }
+        }
+    }
+
+    private final class ProgressMessageController implements Runnable {
+        private static final long PROGRESS_TIMEOUT_MILLIS = 1000;
+
+        private final Handler mHandler;
+
+        private boolean mPosted;
+
+        public ProgressMessageController(Context context) {
+            mHandler = new Handler(context.getMainLooper(), null, false);
+        }
+
+        public void post() {
+            if (mPosted) {
+                return;
+            }
+            mPosted = true;
+            mHandler.postDelayed(this, PROGRESS_TIMEOUT_MILLIS);
+        }
+
+        public void cancel() {
+            if (!mPosted) {
+                return;
+            }
+            mPosted = false;
+            mHandler.removeCallbacks(this);
+        }
+
+        @Override
+        public void run() {
+            ensureProgressUiShown();
+        }
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintErrorFragment.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintErrorFragment.java
new file mode 100644
index 0000000..b708356
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintErrorFragment.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.printspooler.ui;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.printspooler.R;
+
+/**
+ * Fragment for showing an error UI.
+ */
+public final class PrintErrorFragment extends Fragment {
+    public static final int ACTION_NONE = 0;
+    public static final int ACTION_RETRY = 1;
+    public static final int ACTION_CONFIRM = 2;
+
+    public interface OnActionListener {
+        public void onActionPerformed();
+    }
+
+    private static final String EXTRA_ERROR_MESSAGE = "EXTRA_ERROR_MESSAGE";
+    private static final String EXTRA_ACTION = "EXTRA_ACTION";
+
+    public static PrintErrorFragment newInstance(CharSequence errorMessage, int action) {
+        PrintErrorFragment instance = new PrintErrorFragment();
+        Bundle arguments = new Bundle();
+        arguments.putCharSequence(EXTRA_ERROR_MESSAGE, errorMessage);
+        arguments.putInt(EXTRA_ACTION, action);
+        instance.setArguments(arguments);
+        return instance;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup root,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.print_error_fragment, root, false);
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        Bundle arguments = getArguments();
+
+        CharSequence error = arguments.getString(EXTRA_ERROR_MESSAGE);
+        if (!TextUtils.isEmpty(error)) {
+            TextView message = (TextView) view.findViewById(R.id.message);
+            message.setText(error);
+        }
+
+        Button actionButton = (Button) view.findViewById(R.id.action_button);
+
+        final int action = getArguments().getInt(EXTRA_ACTION);
+        switch (action) {
+            case ACTION_RETRY: {
+                actionButton.setVisibility(View.VISIBLE);
+                actionButton.setText(R.string.print_error_retry);
+            } break;
+
+            case ACTION_CONFIRM: {
+                actionButton.setVisibility(View.VISIBLE);
+                actionButton.setText(android.R.string.ok);
+            } break;
+
+            case ACTION_NONE: {
+                actionButton.setVisibility(View.GONE);
+            } break;
+        }
+
+        actionButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Activity activity = getActivity();
+                if (activity instanceof OnActionListener) {
+                    ((OnActionListener) getActivity()).onActionPerformed();
+                }
+            }
+        });
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewFragment.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewFragment.java
new file mode 100644
index 0000000..d68a6aa
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewFragment.java
@@ -0,0 +1,12 @@
+package com.android.printspooler.ui;
+
+import android.app.Fragment;
+
+public class PrintPreviewFragment extends Fragment {
+
+    public static PrintPreviewFragment newInstance() {
+        return new PrintPreviewFragment();
+    }
+
+    // TODO: Implement
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintProgressFragment.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintProgressFragment.java
new file mode 100644
index 0000000..96aa153d
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintProgressFragment.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.printspooler.ui;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+import android.widget.TextView;
+import com.android.printspooler.R;
+
+/**
+ * Fragment for showing a work in progress UI.
+ */
+public final class PrintProgressFragment extends Fragment {
+
+    public interface OnCancelRequestListener {
+        public void onCancelRequest();
+    }
+
+    public static PrintProgressFragment newInstance() {
+        return new PrintProgressFragment();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup root,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.print_progress_fragment, root, false);
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        final Button cancelButton = (Button) view.findViewById(R.id.cancel_button);
+        final TextView message = (TextView) view.findViewById(R.id.message);
+
+        cancelButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Activity activity = getActivity();
+                if (activity instanceof OnCancelRequestListener) {
+                    ((OnCancelRequestListener) getActivity()).onCancelRequest();
+                }
+                cancelButton.setVisibility(View.GONE);
+                message.setVisibility(View.VISIBLE);
+            }
+        });
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java
new file mode 100644
index 0000000..7816d66
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.printspooler.ui;
+
+import android.app.Activity;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Loader;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PrinterRegistry {
+
+    private static final int LOADER_ID_PRINTERS_LOADER = 1;
+
+    private final Activity mActivity;
+
+    private final List<PrinterInfo> mPrinters = new ArrayList<>();
+
+    private final Runnable mReadyCallback;
+
+    private final Handler mHandler;
+
+    private boolean mReady;
+
+    private OnPrintersChangeListener mOnPrintersChangeListener;
+
+    public interface OnPrintersChangeListener {
+        public void onPrintersChanged(List<PrinterInfo> printers);
+        public void onPrintersInvalid();
+    }
+
+    public PrinterRegistry(Activity activity, Runnable readyCallback) {
+        mActivity = activity;
+        mReadyCallback = readyCallback;
+        mHandler = new MyHandler(activity.getMainLooper());
+        activity.getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER,
+                null, mLoaderCallbacks);
+    }
+
+    public void setOnPrintersChangeListener(OnPrintersChangeListener listener) {
+        mOnPrintersChangeListener = listener;
+    }
+
+    public List<PrinterInfo> getPrinters() {
+        return mPrinters;
+    }
+
+    public void addHistoricalPrinter(PrinterInfo printer) {
+        getPrinterProvider().addHistoricalPrinter(printer);
+    }
+
+    public void forgetFavoritePrinter(PrinterId printerId) {
+        getPrinterProvider().forgetFavoritePrinter(printerId);
+    }
+
+    public boolean isFavoritePrinter(PrinterId printerId) {
+        return getPrinterProvider().isFavoritePrinter(printerId);
+    }
+
+    public void setTrackedPrinter(PrinterId printerId) {
+        getPrinterProvider().setTrackedPrinter(printerId);
+    }
+
+    private FusedPrintersProvider getPrinterProvider() {
+        Loader<?> loader = mActivity.getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER);
+        return (FusedPrintersProvider) loader;
+    }
+
+    private final LoaderCallbacks<List<PrinterInfo>> mLoaderCallbacks =
+            new LoaderCallbacks<List<PrinterInfo>>() {
+        @Override
+        public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
+            if (loader.getId() == LOADER_ID_PRINTERS_LOADER) {
+                mPrinters.clear();
+                if (mOnPrintersChangeListener != null) {
+                    // Post a message as we are in onLoadFinished and certain operations
+                    // are not allowed in this callback, such as fragment transactions.
+                    // Clients should not handle this explicitly.
+                    mHandler.obtainMessage(MyHandler.MSG_PRINTERS_INVALID,
+                            mOnPrintersChangeListener).sendToTarget();
+                }
+            }
+        }
+
+        // LoaderCallbacks#onLoadFinished
+        @Override
+        public void onLoadFinished(Loader<List<PrinterInfo>> loader, List<PrinterInfo> printers) {
+            if (loader.getId() == LOADER_ID_PRINTERS_LOADER) {
+                mPrinters.clear();
+                mPrinters.addAll(printers);
+                if (mOnPrintersChangeListener != null) {
+                    // Post a message as we are in onLoadFinished and certain operations
+                    // are not allowed in this callback, such as fragment transactions.
+                    // Clients should not handle this explicitly.
+                    SomeArgs args = SomeArgs.obtain();
+                    args.arg1 = mOnPrintersChangeListener;
+                    args.arg2 = printers;
+                    mHandler.obtainMessage(MyHandler.MSG_PRINTERS_CHANGED, args).sendToTarget();
+                }
+                if (!mReady) {
+                    mReady = true;
+                    if (mReadyCallback != null) {
+                        mReadyCallback.run();
+                    }
+                }
+            }
+        }
+
+        // LoaderCallbacks#onCreateLoader
+        @Override
+        public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
+            if (id == LOADER_ID_PRINTERS_LOADER) {
+                return new FusedPrintersProvider(mActivity);
+            }
+            return null;
+        }
+    };
+
+    private static final class MyHandler extends Handler {
+        public static final int MSG_PRINTERS_CHANGED = 0;
+        public static final int MSG_PRINTERS_INVALID = 1;
+
+        public MyHandler(Looper looper) {
+            super(looper, null , false);
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public void handleMessage(Message message) {
+            switch (message.what) {
+                case MSG_PRINTERS_CHANGED: {
+                    SomeArgs args = (SomeArgs) message.obj;
+                    OnPrintersChangeListener callback = (OnPrintersChangeListener) args.arg1;
+                    List<PrinterInfo> printers = (List<PrinterInfo>) args.arg2;
+                    args.recycle();
+                    callback.onPrintersChanged(printers);
+                } break;
+
+                case MSG_PRINTERS_INVALID: {
+                    OnPrintersChangeListener callback = (OnPrintersChangeListener) message.obj;
+                    callback.onPrintersInvalid();
+                } break;
+            }
+        }
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
similarity index 73%
rename from packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
rename to packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index fe5920c..7715579 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.printspooler;
+package com.android.printspooler.ui;
 
 import android.app.Activity;
 import android.app.AlertDialog;
@@ -22,13 +22,11 @@
 import android.app.DialogFragment;
 import android.app.Fragment;
 import android.app.FragmentTransaction;
-import android.app.LoaderManager;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.Loader;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -48,9 +46,7 @@
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
-import android.view.LayoutInflater;
 import android.view.Menu;
-import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
@@ -66,63 +62,60 @@
 import android.widget.SearchView;
 import android.widget.TextView;
 
+import com.android.printspooler.R;
+
 import java.util.ArrayList;
 import java.util.List;
 
 /**
- * This is a fragment for selecting a printer.
+ * This is an activity for selecting a printer.
  */
-public final class SelectPrinterFragment extends Fragment {
+public final class SelectPrinterActivity extends Activity {
 
     private static final String LOG_TAG = "SelectPrinterFragment";
 
-    private static final int LOADER_ID_PRINTERS_LOADER = 1;
+    public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
 
-    private static final String FRAGMRNT_TAG_ADD_PRINTER_DIALOG =
-            "FRAGMRNT_TAG_ADD_PRINTER_DIALOG";
+    private static final String FRAGMENT_TAG_ADD_PRINTER_DIALOG =
+            "FRAGMENT_TAG_ADD_PRINTER_DIALOG";
 
-    private static final String FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS =
-            "FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS";
+    private static final String FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS =
+            "FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS";
 
     private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID";
 
     private final ArrayList<PrintServiceInfo> mAddPrinterServices =
-            new ArrayList<PrintServiceInfo>();
+            new ArrayList<>();
+
+    private PrinterRegistry mPrinterRegistry;
 
     private ListView mListView;
 
     private AnnounceFilterResult mAnnounceFilterResult;
 
-    public static interface OnPrinterSelectedListener {
-        public void onPrinterSelected(PrinterId printerId);
-    }
-
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setHasOptionsMenu(true);
-        getActivity().getActionBar().setIcon(R.drawable.ic_menu_print);
-    }
+        getActionBar().setIcon(R.drawable.ic_menu_print);
 
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        View content = inflater.inflate(R.layout.select_printer_fragment, container, false);
+        setContentView(R.layout.select_printer_activity);
+
+        mPrinterRegistry = new PrinterRegistry(this, null);
 
         // Hook up the list view.
-        mListView = (ListView) content.findViewById(android.R.id.list);
+        mListView = (ListView) findViewById(android.R.id.list);
         final DestinationAdapter adapter = new DestinationAdapter();
         adapter.registerDataSetObserver(new DataSetObserver() {
             @Override
             public void onChanged() {
-                if (!getActivity().isFinishing() && adapter.getCount() <= 0) {
+                if (!isFinishing() && adapter.getCount() <= 0) {
                     updateEmptyView(adapter);
                 }
             }
 
             @Override
             public void onInvalidated() {
-                if (!getActivity().isFinishing()) {
+                if (!isFinishing()) {
                     updateEmptyView(adapter);
                 }
             }
@@ -135,26 +128,20 @@
                 if (!((DestinationAdapter) mListView.getAdapter()).isActionable(position)) {
                     return;
                 }
+
                 PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position);
-                Activity activity = getActivity();
-                if (activity instanceof OnPrinterSelectedListener) {
-                    ((OnPrinterSelectedListener) activity).onPrinterSelected(printer.getId());
-                } else {
-                    throw new IllegalStateException("the host activity must implement"
-                            + " OnPrinterSelectedListener");
-                }
+                onPrinterSelected(printer.getId());
             }
         });
 
         registerForContextMenu(mListView);
-
-        return content;
     }
 
     @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        super.onCreateOptionsMenu(menu, inflater);
-        inflater.inflate(R.menu.select_printer_activity, menu);
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+
+        getMenuInflater().inflate(R.menu.select_printer_activity, menu);
 
         MenuItem searchItem = menu.findItem(R.id.action_search);
         SearchView searchView = (SearchView) searchItem.getActionView();
@@ -173,16 +160,15 @@
         searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View view) {
-                if (AccessibilityManager.getInstance(getActivity()).isEnabled()) {
+                if (AccessibilityManager.getInstance(SelectPrinterActivity.this).isEnabled()) {
                     view.announceForAccessibility(getString(
                             R.string.print_search_box_shown_utterance));
                 }
             }
             @Override
             public void onViewDetachedFromWindow(View view) {
-                Activity activity = getActivity();
-                if (activity != null && !activity.isFinishing()
-                        && AccessibilityManager.getInstance(activity).isEnabled()) {
+                if (!isFinishing() && AccessibilityManager.getInstance(
+                        SelectPrinterActivity.this).isEnabled()) {
                     view.announceForAccessibility(getString(
                             R.string.print_search_box_hidden_utterance));
                 }
@@ -192,6 +178,8 @@
         if (mAddPrinterServices.isEmpty()) {
             menu.removeItem(R.id.action_add_printer);
         }
+
+        return true;
     }
 
     @Override
@@ -212,9 +200,7 @@
             }
 
             // Add the forget menu item if applicable.
-            FusedPrintersProvider provider = (FusedPrintersProvider) (Loader<?>)
-                    getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER);
-            if (provider.isFavoritePrinter(printer.getId())) {
+            if (mPrinterRegistry.isFavoritePrinter(printer.getId())) {
                 MenuItem forgetItem = menu.add(Menu.NONE, R.string.print_forget_printer,
                         Menu.NONE, R.string.print_forget_printer);
                 Intent intent = new Intent();
@@ -228,23 +214,13 @@
     public boolean onContextItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case R.string.print_select_printer: {
-                PrinterId printerId = (PrinterId) item.getIntent().getParcelableExtra(
-                        EXTRA_PRINTER_ID);
-                Activity activity = getActivity();
-                if (activity instanceof OnPrinterSelectedListener) {
-                    ((OnPrinterSelectedListener) activity).onPrinterSelected(printerId);
-                } else {
-                    throw new IllegalStateException("the host activity must implement"
-                            + " OnPrinterSelectedListener");
-                }
+                PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID);
+                onPrinterSelected(printerId);
             } return true;
 
             case R.string.print_forget_printer: {
-                PrinterId printerId = (PrinterId) item.getIntent().getParcelableExtra(
-                        EXTRA_PRINTER_ID);
-                FusedPrintersProvider provider = (FusedPrintersProvider) (Loader<?>)
-                        getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER);
-                provider.forgetFavoritePrinter(printerId);
+                PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID);
+                mPrinterRegistry.forgetFavoritePrinter(printerId);
             } return true;
         }
         return false;
@@ -252,9 +228,9 @@
 
     @Override
     public void onResume() {
-        updateAddPrintersAdapter();
-        getActivity().invalidateOptionsMenu();
         super.onResume();
+        updateServicesWithAddPrinterActivity();
+        invalidateOptionsMenu();
     }
 
     @Override
@@ -274,12 +250,18 @@
         return super.onOptionsItemSelected(item);
     }
 
-    private void updateAddPrintersAdapter() {
+    private void onPrinterSelected(PrinterId printerId) {
+        Intent intent = new Intent();
+        intent.putExtra(INTENT_EXTRA_PRINTER_ID, printerId);
+        setResult(RESULT_OK, intent);
+        finish();
+    }
+
+    private void updateServicesWithAddPrinterActivity() {
         mAddPrinterServices.clear();
 
         // Get all enabled print services.
-        PrintManager printManager = (PrintManager) getActivity()
-                .getSystemService(Context.PRINT_SERVICE);
+        PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
         List<PrintServiceInfo> enabledServices = printManager.getEnabledPrintServices();
 
         // No enabled print services - done.
@@ -292,7 +274,7 @@
         for (int i = 0; i < enabledServiceCount; i++) {
             PrintServiceInfo enabledService = enabledServices.get(i);
 
-            // No add printers activity declared - done.
+            // No add printers activity declared - next.
             if (TextUtils.isEmpty(enabledService.getAddPrintersActivityName())) {
                 continue;
             }
@@ -304,15 +286,14 @@
                 .setComponent(addPrintersComponentName);
 
             // The add printers activity is valid - add it.
-            PackageManager pm = getActivity().getPackageManager();
+            PackageManager pm = getPackageManager();
             List<ResolveInfo> resolvedActivities = pm.queryIntentActivities(addPritnersIntent, 0);
             if (!resolvedActivities.isEmpty()) {
                 // The activity is a component name, therefore it is one or none.
                 ActivityInfo activityInfo = resolvedActivities.get(0).activityInfo;
                 if (activityInfo.exported
                         && (activityInfo.permission == null
-                                || pm.checkPermission(activityInfo.permission,
-                                        getActivity().getPackageName())
+                                || pm.checkPermission(activityInfo.permission, getPackageName())
                                         == PackageManager.PERMISSION_GRANTED)) {
                     mAddPrinterServices.add(enabledService);
                 }
@@ -323,26 +304,26 @@
     private void showAddPrinterSelectionDialog() {
         FragmentTransaction transaction = getFragmentManager().beginTransaction();
         Fragment oldFragment = getFragmentManager().findFragmentByTag(
-                FRAGMRNT_TAG_ADD_PRINTER_DIALOG);
+                FRAGMENT_TAG_ADD_PRINTER_DIALOG);
         if (oldFragment != null) {
             transaction.remove(oldFragment);
         }
         AddPrinterAlertDialogFragment newFragment = new AddPrinterAlertDialogFragment();
         Bundle arguments = new Bundle();
-        arguments.putParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS,
+        arguments.putParcelableArrayList(FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS,
                 mAddPrinterServices);
         newFragment.setArguments(arguments);
-        transaction.add(newFragment, FRAGMRNT_TAG_ADD_PRINTER_DIALOG);
+        transaction.add(newFragment, FRAGMENT_TAG_ADD_PRINTER_DIALOG);
         transaction.commit();
     }
 
     public void updateEmptyView(DestinationAdapter adapter) {
         if (mListView.getEmptyView() == null) {
-            View emptyView = getActivity().findViewById(R.id.empty_print_state);
+            View emptyView = findViewById(R.id.empty_print_state);
             mListView.setEmptyView(emptyView);
         }
-        TextView titleView = (TextView) getActivity().findViewById(R.id.title);
-        View progressBar = getActivity().findViewById(R.id.progress_bar);
+        TextView titleView = (TextView) findViewById(R.id.title);
+        View progressBar = findViewById(R.id.progress_bar);
         if (adapter.getUnfilteredCount() <= 0) {
             titleView.setText(R.string.print_searching_for_printers);
             progressBar.setVisibility(View.VISIBLE);
@@ -353,7 +334,7 @@
     }
 
     private void announceSearchResultIfNeeded() {
-        if (AccessibilityManager.getInstance(getActivity()).isEnabled()) {
+        if (AccessibilityManager.getInstance(this).isEnabled()) {
             if (mAnnounceFilterResult == null) {
                 mAnnounceFilterResult = new AnnounceFilterResult();
             }
@@ -372,9 +353,9 @@
                     .setTitle(R.string.choose_print_service);
 
             final List<PrintServiceInfo> printServices = (List<PrintServiceInfo>) (List<?>)
-                    getArguments().getParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS);
+                    getArguments().getParcelableArrayList(FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS);
 
-            final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
+            final ArrayAdapter<String> adapter = new ArrayAdapter<>(
                     getActivity(), android.R.layout.simple_list_item_1);
             final int printServiceCount = printServices.size();
             for (int i = 0; i < printServiceCount; i++) {
@@ -382,32 +363,33 @@
                 adapter.add(printService.getResolveInfo().loadLabel(
                         getActivity().getPackageManager()).toString());
             }
+
             final String searchUri = Settings.Secure.getString(getActivity().getContentResolver(),
                     Settings.Secure.PRINT_SERVICE_SEARCH_URI);
-            final Intent marketIntent;
+            final Intent viewIntent;
             if (!TextUtils.isEmpty(searchUri)) {
                 Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
                 if (getActivity().getPackageManager().resolveActivity(intent, 0) != null) {
-                    marketIntent = intent;
+                    viewIntent = intent;
                     mAddPrintServiceItem = getString(R.string.add_print_service_label);
                     adapter.add(mAddPrintServiceItem);
                 } else {
-                    marketIntent = null;
+                    viewIntent = null;
                 }
             } else {
-                marketIntent = null;
+                viewIntent = null;
             }
 
             builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
                 @Override
                 public void onClick(DialogInterface dialog, int which) {
                     String item = adapter.getItem(which);
-                    if (item == mAddPrintServiceItem) {
+                    if (item.equals(mAddPrintServiceItem)) {
                         try {
-                          startActivity(marketIntent);
-                      } catch (ActivityNotFoundException anfe) {
-                          Log.w(LOG_TAG, "Couldn't start add printer activity", anfe);
-                      }
+                            startActivity(viewIntent);
+                        } catch (ActivityNotFoundException anfe) {
+                            Log.w(LOG_TAG, "Couldn't start add printer activity", anfe);
+                        }
                     } else {
                         PrintServiceInfo printService = printServices.get(which);
                         ComponentName componentName = new ComponentName(
@@ -418,7 +400,7 @@
                         try {
                             startActivity(intent);
                         } catch (ActivityNotFoundException anfe) {
-                            Log.w(LOG_TAG, "Couldn't start settings activity", anfe);
+                            Log.w(LOG_TAG, "Couldn't start add printer activity", anfe);
                         }
                     }
                 }
@@ -428,19 +410,41 @@
         }
     }
 
-    private final class DestinationAdapter extends BaseAdapter
-            implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>, Filterable {
+    private final class DestinationAdapter extends BaseAdapter implements Filterable {
 
         private final Object mLock = new Object();
 
-        private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
+        private final List<PrinterInfo> mPrinters = new ArrayList<>();
 
-        private final List<PrinterInfo> mFilteredPrinters = new ArrayList<PrinterInfo>();
+        private final List<PrinterInfo> mFilteredPrinters = new ArrayList<>();
 
         private CharSequence mLastSearchString;
 
         public DestinationAdapter() {
-            getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
+            mPrinterRegistry.setOnPrintersChangeListener(new PrinterRegistry.OnPrintersChangeListener() {
+                @Override
+                public void onPrintersChanged(List<PrinterInfo> printers) {
+                    synchronized (mLock) {
+                        mPrinters.clear();
+                        mPrinters.addAll(printers);
+                        mFilteredPrinters.clear();
+                        mFilteredPrinters.addAll(printers);
+                        if (!TextUtils.isEmpty(mLastSearchString)) {
+                            getFilter().filter(mLastSearchString);
+                        }
+                    }
+                    notifyDataSetChanged();
+                }
+
+                @Override
+                public void onPrintersInvalid() {
+                    synchronized (mLock) {
+                        mPrinters.clear();
+                        mFilteredPrinters.clear();
+                    }
+                    notifyDataSetInvalidated();
+                }
+            });
         }
 
         @Override
@@ -453,7 +457,7 @@
                             return null;
                         }
                         FilterResults results = new FilterResults();
-                        List<PrinterInfo> filteredPrinters = new ArrayList<PrinterInfo>();
+                        List<PrinterInfo> filteredPrinters = new ArrayList<>();
                         String constraintLowerCase = constraint.toString().toLowerCase();
                         final int printerCount = mPrinters.size();
                         for (int i = 0; i < printerCount; i++) {
@@ -518,28 +522,27 @@
         }
 
         @Override
-        public View getDropDownView(int position, View convertView,
-                ViewGroup parent) {
+        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 = getActivity().getLayoutInflater().inflate(
+                convertView = getLayoutInflater().inflate(
                         R.layout.printer_list_item, parent, false);
             }
 
             convertView.setEnabled(isActionable(position));
 
-            CharSequence title = null;
+            PrinterInfo printer = (PrinterInfo) getItem(position);
+
+            CharSequence title = printer.getName();
             CharSequence subtitle = null;
             Drawable icon = null;
 
-            PrinterInfo printer = (PrinterInfo) getItem(position);
-            title = printer.getName();
             try {
-                PackageManager pm = getActivity().getPackageManager();
+                PackageManager pm = getPackageManager();
                 PackageInfo packageInfo = pm.getPackageInfo(printer.getId()
                         .getServiceName().getPackageName(), 0);
                 subtitle = packageInfo.applicationInfo.loadLabel(pm);
@@ -576,38 +579,6 @@
             PrinterInfo printer =  (PrinterInfo) getItem(position);
             return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
         }
-
-        @Override
-        public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
-            if (id == LOADER_ID_PRINTERS_LOADER) {
-                return new FusedPrintersProvider(getActivity());
-            }
-            return null;
-        }
-
-        @Override
-        public void onLoadFinished(Loader<List<PrinterInfo>> loader,
-                List<PrinterInfo> printers) {
-            synchronized (mLock) {
-                mPrinters.clear();
-                mPrinters.addAll(printers);
-                mFilteredPrinters.clear();
-                mFilteredPrinters.addAll(printers);
-                if (!TextUtils.isEmpty(mLastSearchString)) {
-                    getFilter().filter(mLastSearchString);
-                }
-            }
-            notifyDataSetChanged();
-        }
-
-        @Override
-        public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
-            synchronized (mLock) {
-                mPrinters.clear();
-                mFilteredPrinters.clear();
-            }
-            notifyDataSetInvalidated();
-        }
     }
 
     private final class AnnounceFilterResult implements Runnable {
@@ -629,7 +600,7 @@
             if (count <= 0) {
                 text = getString(R.string.print_no_printers);
             } else {
-                text = getActivity().getResources().getQuantityString(
+                text = getResources().getQuantityString(
                     R.plurals.print_search_result_count_utterance, count, count);
             }
             mListView.announceForAccessibility(text);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/MediaSizeUtils.java b/packages/PrintSpooler/src/com/android/printspooler/util/MediaSizeUtils.java
similarity index 95%
rename from packages/PrintSpooler/src/com/android/printspooler/MediaSizeUtils.java
rename to packages/PrintSpooler/src/com/android/printspooler/util/MediaSizeUtils.java
index ac27562..912ee1d 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/MediaSizeUtils.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/util/MediaSizeUtils.java
@@ -14,22 +14,28 @@
  * limitations under the License.
  */
 
-package com.android.printspooler;
+package com.android.printspooler.util;
 
 import android.content.Context;
 import android.print.PrintAttributes.MediaSize;
 import android.util.ArrayMap;
 
+import com.android.printspooler.R;
+
 import java.util.Comparator;
 import java.util.Map;
 
 /**
  * Utility functions and classes for dealing with media sizes.
  */
-public class MediaSizeUtils {
+public final class MediaSizeUtils {
 
     private static Map<MediaSize, String> sMediaSizeToStandardMap;
 
+    private MediaSizeUtils() {
+        /* do nothing - hide constructor */
+    }
+
     /**
      * Gets the default media size for the current locale.
      *
diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
new file mode 100644
index 0000000..33b294f
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.printspooler.util;
+
+import android.print.PageRange;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * This class contains utility functions for working with page ranges.
+ */
+public final class PageRangeUtils {
+
+    private static final PageRange[] ALL_PAGES_RANGE = new PageRange[] {PageRange.ALL_PAGES};
+
+    private static final Comparator<PageRange> sComparator = new Comparator<PageRange>() {
+        @Override
+        public int compare(PageRange lhs, PageRange rhs) {
+            return lhs.getStart() - rhs.getStart();
+        }
+    };
+
+    private PageRangeUtils() {
+        /* do nothing - hide constructor */
+    }
+
+    /**
+     * Checks whether one page range array contains another one.
+     *
+     * @param ourRanges The container page ranges.
+     * @param otherRanges The contained page ranges.
+     * @return Whether the container page ranges contains the contained ones.
+     */
+    public static boolean contains(PageRange[] ourRanges, PageRange[] otherRanges) {
+        if (ourRanges == null || otherRanges == null) {
+            return false;
+        }
+
+        if (Arrays.equals(ourRanges, ALL_PAGES_RANGE)) {
+            return true;
+        }
+
+        ourRanges = normalize(ourRanges);
+        otherRanges = normalize(otherRanges);
+
+        // Note that the code below relies on the ranges being normalized
+        // which is they contain monotonically increasing non-intersecting
+        // sub-ranges whose start is less that or equal to the end.
+        int otherRangeIdx = 0;
+        final int ourRangeCount = ourRanges.length;
+        final int otherRangeCount = otherRanges.length;
+        for (int ourRangeIdx = 0; ourRangeIdx < ourRangeCount; ourRangeIdx++) {
+            PageRange ourRange = ourRanges[ourRangeIdx];
+            for (; otherRangeIdx < otherRangeCount; otherRangeIdx++) {
+                PageRange otherRange = otherRanges[otherRangeIdx];
+                if (otherRange.getStart() > ourRange.getEnd()) {
+                    break;
+                }
+                if (otherRange.getStart() < ourRange.getStart()
+                        || otherRange.getEnd() > ourRange.getEnd()) {
+                    return false;
+                }
+            }
+        }
+        if (otherRangeIdx < otherRangeCount) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Normalizes a page range, which is the resulting page ranges are
+     * non-overlapping with the start lesser than or equal to the end
+     * and ordered in an ascending order.
+     *
+     * @param pageRanges The page ranges to normalize.
+     * @return The normalized page ranges.
+     */
+    public static PageRange[] normalize(PageRange[] pageRanges) {
+        if (pageRanges == null) {
+            return null;
+        }
+        final int oldRangeCount = pageRanges.length;
+        if (oldRangeCount <= 1) {
+            return pageRanges;
+        }
+        Arrays.sort(pageRanges, sComparator);
+        int newRangeCount = 1;
+        for (int i = 0; i < oldRangeCount - 1; i++) {
+            newRangeCount++;
+            PageRange currentRange = pageRanges[i];
+            PageRange nextRange = pageRanges[i + 1];
+            if (currentRange.getEnd() + 1 >= nextRange.getStart()) {
+                newRangeCount--;
+                pageRanges[i] = null;
+                pageRanges[i + 1] = new PageRange(currentRange.getStart(),
+                        Math.max(currentRange.getEnd(), nextRange.getEnd()));
+            }
+        }
+        if (newRangeCount == oldRangeCount) {
+            return pageRanges;
+        }
+        return Arrays.copyOfRange(pageRanges, oldRangeCount - newRangeCount,
+                oldRangeCount);
+    }
+
+    /**
+     * Offsets a the start and end of page ranges with the given value.
+     *
+     * @param pageRanges The page ranges to offset.
+     * @param offset The offset value.
+     */
+    public static void offset(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);
+        }
+    }
+
+    /**
+     * Gets the number of pages in a normalized range array.
+     *
+     * @param pageRanges Normalized page ranges.
+     * @param layoutPageCount Page count after reported after layout pass.
+     * @return The page count in the ranges.
+     */
+    public static int getNormalizedPageCount(PageRange[] pageRanges, int layoutPageCount) {
+        int pageCount = 0;
+        final int pageRangeCount = pageRanges.length;
+        for (int i = 0; i < pageRangeCount; i++) {
+            PageRange pageRange = pageRanges[i];
+            if (PageRange.ALL_PAGES.equals(pageRange)) {
+                return layoutPageCount;
+            }
+            pageCount += pageRange.getEnd() - pageRange.getStart() + 1;
+        }
+        return pageCount;
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/PrintOptionUtils.java b/packages/PrintSpooler/src/com/android/printspooler/util/PrintOptionUtils.java
new file mode 100644
index 0000000..446952d
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/util/PrintOptionUtils.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.printspooler.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ServiceInfo;
+import android.print.PrintManager;
+import android.printservice.PrintServiceInfo;
+
+import java.util.List;
+
+public class PrintOptionUtils {
+
+    private PrintOptionUtils() {
+        /* ignore - hide constructor */
+    }
+
+    /**
+     * Gets the advanced options activity name for a print service.
+     *
+     * @param context Context for accessing system resources.
+     * @param serviceName The print service name.
+     * @return The advanced options activity name or null.
+     */
+    public static String getAdvancedOptionsActivityName(Context context,
+            ComponentName serviceName) {
+        PrintManager printManager = (PrintManager) context.getSystemService(
+                Context.PRINT_SERVICE);
+        List<PrintServiceInfo> printServices = printManager.getEnabledPrintServices();
+        final int printServiceCount = printServices.size();
+        for (int i = 0; i < printServiceCount; i ++) {
+            PrintServiceInfo printServiceInfo = printServices.get(i);
+            ServiceInfo serviceInfo = printServiceInfo.getResolveInfo().serviceInfo;
+            if (serviceInfo.name.equals(serviceName.getClassName())
+                    && serviceInfo.packageName.equals(serviceName.getPackageName())) {
+                return printServiceInfo.getAdvancedOptionsActivityName();
+            }
+        }
+        return null;
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/ContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/ContentView.java
new file mode 100644
index 0000000..77ca541
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/ContentView.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.printspooler.widget;
+
+import android.content.Context;
+import android.support.v4.widget.ViewDragHelper;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.printspooler.R;
+
+/**
+ * This class is a layout manager for the print screen. It has a sliding
+ * area that contains the print options. If the sliding area is open the
+ * print options are visible and if it is closed a summary of the print
+ * job is shown. Under the sliding area there is a place for putting
+ * arbitrary content such as preview, error message, progress indicator,
+ * etc. The sliding area is covering the content holder under it when
+ * the former is opened.
+ */
+@SuppressWarnings("unused")
+public final class ContentView extends ViewGroup implements View.OnClickListener {
+    private static final int FIRST_POINTER_ID = 0;
+
+    private final ViewDragHelper mDragger;
+
+    private View mStaticContent;
+    private ViewGroup mSummaryContent;
+    private View mDynamicContent;
+
+    private View mDraggableContent;
+    private ViewGroup mMoreOptionsContainer;
+    private ViewGroup mOptionsContainer;
+
+    private View mEmbeddedContentContainer;
+
+    private View mExpandCollapseHandle;
+    private View mExpandCollapseIcon;
+
+    private int mClosedOptionsOffsetY;
+    private int mCurrentOptionsOffsetY;
+
+    private OptionsStateChangeListener mOptionsStateChangeListener;
+
+    private int mOldDraggableHeight;
+
+    public interface OptionsStateChangeListener {
+        public void onOptionsOpened();
+        public void onOptionsClosed();
+    }
+
+    public ContentView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mDragger = ViewDragHelper.create(this, new DragCallbacks());
+
+        // The options view is sliding under the static header but appears
+        // after it in the layout, so we will draw in opposite order.
+        setChildrenDrawingOrderEnabled(true);
+    }
+
+    public void setOptionsStateChangeListener(OptionsStateChangeListener listener) {
+        mOptionsStateChangeListener = listener;
+    }
+
+    private boolean isOptionsOpened() {
+        return mCurrentOptionsOffsetY == 0;
+    }
+
+    private boolean isOptionsClosed() {
+        return mCurrentOptionsOffsetY == mClosedOptionsOffsetY;
+    }
+
+    private void openOptions() {
+        if (isOptionsOpened()) {
+            return;
+        }
+        mDragger.smoothSlideViewTo(mDynamicContent, mDynamicContent.getLeft(),
+                getOpenedOptionsY());
+        invalidate();
+    }
+
+    private void closeOptions() {
+        if (isOptionsClosed()) {
+            return;
+        }
+        mDragger.smoothSlideViewTo(mDynamicContent, mDynamicContent.getLeft(),
+                getClosedOptionsY());
+        invalidate();
+    }
+
+    @Override
+    protected int getChildDrawingOrder(int childCount, int i) {
+        return childCount - i - 1;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        mStaticContent = findViewById(R.id.static_content);
+        mSummaryContent = (ViewGroup) findViewById(R.id.summary_content);
+        mDynamicContent = findViewById(R.id.dynamic_content);
+        mDraggableContent = findViewById(R.id.draggable_content);
+        mMoreOptionsContainer = (ViewGroup) findViewById(R.id.more_options_container);
+        mOptionsContainer = (ViewGroup) findViewById(R.id.options_container);
+        mEmbeddedContentContainer = findViewById(R.id.embedded_content_container);
+        mExpandCollapseIcon = findViewById(R.id.expand_collapse_icon);
+        mExpandCollapseHandle = findViewById(R.id.expand_collapse_handle);
+
+        mExpandCollapseIcon.setOnClickListener(this);
+        mExpandCollapseHandle.setOnClickListener(this);
+
+        // Make sure we start in a closed options state.
+        onDragProgress(1.0f);
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (view == mExpandCollapseHandle || view == mExpandCollapseIcon) {
+            if (isOptionsClosed()) {
+                openOptions();
+            } else if (isOptionsOpened()) {
+                closeOptions();
+            } // else in open/close progress do nothing.
+        }
+    }
+
+    @Override
+    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        /* do nothing */
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        mDragger.processTouchEvent(event);
+        return true;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        return mDragger.shouldInterceptTouchEvent(event)
+                || super.onInterceptTouchEvent(event);
+    }
+
+    @Override
+    public void computeScroll() {
+        if (mDragger.continueSettling(true)) {
+            postInvalidateOnAnimation();
+        }
+    }
+
+    private int getOpenedOptionsY() {
+        return mStaticContent.getBottom();
+    }
+
+    private int getClosedOptionsY() {
+        return getOpenedOptionsY() + mClosedOptionsOffsetY;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        measureChild(mStaticContent, widthMeasureSpec, heightMeasureSpec);
+
+        if (mSummaryContent.getVisibility() != View.GONE) {
+            measureChild(mSummaryContent, widthMeasureSpec, heightMeasureSpec);
+        }
+
+        measureChild(mDynamicContent, widthMeasureSpec, heightMeasureSpec);
+
+        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+//        // The height of the draggable content may change and if that happens
+//        // we have to adjust the current offset to ensure the sliding area is
+//        // at the same position.
+//        mCurrentOptionsOffsetY -= mDraggableContent.getMeasuredHeight()
+//                - oldDraggableHeight;
+
+        if (mOldDraggableHeight != mDraggableContent.getMeasuredHeight()) {
+            mCurrentOptionsOffsetY -= mDraggableContent.getMeasuredHeight()
+                    - mOldDraggableHeight;
+            mOldDraggableHeight = mDraggableContent.getMeasuredHeight();
+        }
+
+        // The height of the draggable content may change and if that happens
+        // we have to adjust the sliding area closed state offset.
+        mClosedOptionsOffsetY = mSummaryContent.getMeasuredHeight()
+                - mDraggableContent.getMeasuredHeight();
+
+        // The content host must be maximally large size that fits entirely
+        // on the screen when the options are collapsed.
+        ViewGroup.LayoutParams params = mEmbeddedContentContainer.getLayoutParams();
+        if (params.height == 0) {
+            params.height = heightSize - mStaticContent.getMeasuredHeight()
+                    - mSummaryContent.getMeasuredHeight() - mDynamicContent.getMeasuredHeight()
+                    + mDraggableContent.getMeasuredHeight();
+
+            mCurrentOptionsOffsetY = mClosedOptionsOffsetY;
+        }
+
+        // The content host can grow vertically as much as needed - we will be covering it.
+        final int hostHeightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0);
+        measureChild(mEmbeddedContentContainer, widthMeasureSpec, hostHeightMeasureSpec);
+
+        setMeasuredDimension(resolveSize(MeasureSpec.getSize(widthMeasureSpec), widthMeasureSpec),
+                resolveSize(heightSize, heightMeasureSpec));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        mStaticContent.layout(left, top, right, mStaticContent.getMeasuredHeight());
+
+        if (mSummaryContent.getVisibility() != View.GONE) {
+            mSummaryContent.layout(left, mStaticContent.getMeasuredHeight(), right,
+                    mStaticContent.getMeasuredHeight() + mSummaryContent.getMeasuredHeight());
+        }
+
+        final int dynContentTop = mStaticContent.getMeasuredHeight() + mCurrentOptionsOffsetY;
+        final int dynContentBottom = dynContentTop + mDynamicContent.getMeasuredHeight();
+
+        mDynamicContent.layout(left, dynContentTop, right, dynContentBottom);
+
+        final int embContentTop = mStaticContent.getMeasuredHeight() + mClosedOptionsOffsetY
+                + mDynamicContent.getMeasuredHeight();
+        final int embContentBottom = embContentTop + mEmbeddedContentContainer.getMeasuredHeight();
+
+        mEmbeddedContentContainer.layout(left, embContentTop, right, embContentBottom);
+    }
+
+    private void onDragProgress(float progress) {
+        final int summaryCount = mSummaryContent.getChildCount();
+        for (int i = 0; i < summaryCount; i++) {
+            View child = mSummaryContent.getChildAt(i);
+            child.setAlpha(progress);
+        }
+
+        if (progress == 0) {
+            if (mOptionsStateChangeListener != null) {
+                mOptionsStateChangeListener.onOptionsOpened();
+            }
+            mSummaryContent.setVisibility(View.GONE);
+            mExpandCollapseIcon.setBackgroundResource(R.drawable.ic_expand_less);
+        } else {
+            mSummaryContent.setVisibility(View.VISIBLE);
+        }
+
+        final float inverseAlpha = 1.0f - progress;
+
+        final int optionCount = mOptionsContainer.getChildCount();
+        for (int i = 0; i < optionCount; i++) {
+            View child = mOptionsContainer.getChildAt(i);
+            child.setAlpha(inverseAlpha);
+        }
+
+        if (mMoreOptionsContainer.getVisibility() != View.GONE) {
+            final int moreOptionCount = mMoreOptionsContainer.getChildCount();
+            for (int i = 0; i < moreOptionCount; i++) {
+                View child = mMoreOptionsContainer.getChildAt(i);
+                child.setAlpha(inverseAlpha);
+            }
+        }
+
+        if (inverseAlpha == 0) {
+            if (mOptionsStateChangeListener != null) {
+                mOptionsStateChangeListener.onOptionsClosed();
+            }
+            if (mMoreOptionsContainer.getVisibility() != View.GONE) {
+                mMoreOptionsContainer.setVisibility(View.INVISIBLE);
+            }
+            mDraggableContent.setVisibility(View.INVISIBLE);
+            mExpandCollapseIcon.setBackgroundResource(R.drawable.ic_expand_more);
+        } else {
+            if (mMoreOptionsContainer.getVisibility() != View.GONE) {
+                mMoreOptionsContainer.setVisibility(View.VISIBLE);
+            }
+            mDraggableContent.setVisibility(View.VISIBLE);
+        }
+    }
+
+    private final class DragCallbacks extends ViewDragHelper.Callback {
+        @Override
+        public boolean tryCaptureView(View child, int pointerId) {
+            return child == mDynamicContent && pointerId == FIRST_POINTER_ID;
+        }
+
+        @Override
+        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+            mCurrentOptionsOffsetY += dy;
+            final float progress = ((float) top - getOpenedOptionsY())
+                    / (getClosedOptionsY() - getOpenedOptionsY());
+
+            mDraggableContent.notifySubtreeAccessibilityStateChangedIfNeeded();
+
+            onDragProgress(progress);
+        }
+
+        public void onViewReleased(View child, float velocityX, float velocityY) {
+            final int childTop = child.getTop();
+
+            final int openedOptionsY = getOpenedOptionsY();
+            final int closedOptionsY = getClosedOptionsY();
+
+            if (childTop == openedOptionsY || childTop == closedOptionsY) {
+                return;
+            }
+
+            final int halfRange = closedOptionsY + (openedOptionsY - closedOptionsY) / 2;
+            if (childTop < halfRange) {
+                mDragger.smoothSlideViewTo(child, child.getLeft(), closedOptionsY);
+            } else {
+                mDragger.smoothSlideViewTo(child, child.getLeft(), openedOptionsY);
+            }
+
+            invalidate();
+        }
+
+        public int getViewVerticalDragRange(View child) {
+            return mDraggableContent.getHeight();
+        }
+
+        public int clampViewPositionVertical(View child, int top, int dy) {
+            final int staticOptionBottom = mStaticContent.getBottom();
+            return Math.max(Math.min(top, getOpenedOptionsY()), getClosedOptionsY());
+        }
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/FirstFocusableEditText.java b/packages/PrintSpooler/src/com/android/printspooler/widget/FirstFocusableEditText.java
new file mode 100644
index 0000000..d6bb7c8
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/FirstFocusableEditText.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.printspooler.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.EditText;
+
+/**
+ * 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 final class FirstFocusableEditText extends EditText {
+    private boolean mClickedBeforeFocus;
+    private CharSequence mError;
+
+    public FirstFocusableEditText(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);
+    }
+}
\ No newline at end of file
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintOptionsLayout.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintOptionsLayout.java
new file mode 100644
index 0000000..23c8d08
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintOptionsLayout.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.printspooler.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.printspooler.R;
+
+/**
+ * This class is a layout manager for the print options. The options are
+ * arranged in a configurable number of columns and enough rows to fit all
+ * the options given the column count.
+ */
+@SuppressWarnings("unused")
+public final class PrintOptionsLayout extends ViewGroup {
+
+    private final int mColumnCount;
+
+    public PrintOptionsLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray typedArray = context.obtainStyledAttributes(attrs,
+                R.styleable.PrintOptionsLayout);
+        mColumnCount = typedArray.getInteger(R.styleable.PrintOptionsLayout_columnCount, 0);
+        typedArray.recycle();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+
+        final int columnWidth = (widthSize != 0)
+                ? (widthSize - mPaddingLeft - mPaddingRight) / mColumnCount : 0;
+
+        int width = 0;
+        int height = 0;
+        int childState = 0;
+
+        final int childCount = getChildCount();
+        final int rowCount = childCount / mColumnCount + childCount % mColumnCount;
+
+        for (int row = 0; row < rowCount; row++) {
+            int rowWidth = 0;
+            int rowHeight = 0;
+
+            for (int col = 0; col < mColumnCount; col++) {
+                final int childIndex = row * mColumnCount + col;
+
+                if (childIndex >= childCount) {
+                    break;
+                }
+
+                View child = getChildAt(childIndex);
+
+                if (child.getVisibility() == GONE) {
+                    continue;
+                }
+
+                MarginLayoutParams childParams = (MarginLayoutParams) child.getLayoutParams();
+
+                final int childWidthMeasureSpec;
+                if (columnWidth > 0) {
+                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                            columnWidth - childParams.getMarginStart() - childParams.getMarginEnd(),
+                            MeasureSpec.EXACTLY);
+                } else {
+                    childWidthMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
+                            getPaddingStart() + getPaddingEnd() + width, childParams.width);
+                }
+
+                final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
+                        getPaddingTop() + getPaddingBottom() + height, childParams.height);
+
+                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+
+                childState = combineMeasuredStates(childState, child.getMeasuredState());
+
+                rowWidth += child.getMeasuredWidth() + childParams.getMarginStart()
+                        + childParams.getMarginEnd();
+
+                rowHeight = Math.max(rowHeight, child.getMeasuredHeight() + childParams.topMargin
+                        + childParams.bottomMargin);
+            }
+
+            width = Math.max(width, rowWidth);
+            height += rowHeight;
+        }
+
+        width += getPaddingStart() + getPaddingEnd();
+        width = Math.max(width, getMinimumWidth());
+
+        height += getPaddingTop() + getPaddingBottom();
+        height = Math.max(height, getMinimumHeight());
+
+        setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
+                resolveSizeAndState(height, heightMeasureSpec,
+                        childState << MEASURED_HEIGHT_STATE_SHIFT));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int childCount = getChildCount();
+        final int rowCount = childCount / mColumnCount + childCount % mColumnCount;
+
+        int cellStart = getPaddingStart();
+        int cellTop = getPaddingTop();
+
+        for (int row = 0; row < rowCount; row++) {
+            int rowHeight = 0;
+
+            for (int col = 0; col < mColumnCount; col++) {
+                final int childIndex = row * mColumnCount + col;
+
+                if (childIndex >= childCount) {
+                    break;
+                }
+
+                View child = getChildAt(childIndex);
+
+                if (child.getVisibility() == GONE) {
+                    continue;
+                }
+
+                MarginLayoutParams childParams = (MarginLayoutParams) child.getLayoutParams();
+
+                final int childLeft = cellStart + childParams.getMarginStart();
+                final int childTop = cellTop + childParams.topMargin;
+                final int childRight = childLeft + child.getMeasuredWidth();
+                final int childBottom = childTop + child.getMeasuredHeight();
+
+                child.layout(childLeft, childTop, childRight, childBottom);
+
+                cellStart = childRight + childParams.getMarginEnd();
+
+                rowHeight = Math.max(rowHeight, child.getMeasuredHeight()
+                        + childParams.topMargin + childParams.bottomMargin);
+            }
+
+            cellStart = getPaddingStart();
+            cellTop += cellTop + rowHeight;
+        }
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new ViewGroup.MarginLayoutParams(getContext(), attrs);
+    }
+}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index bf97fc0..a92ab7e 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -192,4 +192,10 @@
     <!-- Default for Settings.Global.DEVICE_NAME $1=BRAND $2=MODEL-->
     <string name="def_device_name">%1$s %2$s</string>
 
+    <!-- Default for Settings.Secure.WAKE_GESTURE_ENABLED -->
+    <bool name="def_wake_gesture_enabled">true</bool>
+
+    <!-- Default for Settings.Global.GUEST_USER_ENABLED -->
+    <bool name="def_guest_user_enabled">true</bool>
+
 </resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 81a1b94..09e6a94 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -70,7 +70,7 @@
     // database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
     // is properly propagated through your change.  Not doing so will result in a loss of user
     // settings.
-    private static final int DATABASE_VERSION = 103;
+    private static final int DATABASE_VERSION = 105;
 
     private Context mContext;
     private int mUserHandle;
@@ -1660,6 +1660,41 @@
             }
             upgradeVersion = 103;
         }
+
+        if (upgradeVersion == 103) {
+            db.beginTransaction();
+            SQLiteStatement stmt = null;
+            try {
+                stmt = db.compileStatement("INSERT OR REPLACE INTO secure(name,value)"
+                        + " VALUES(?,?);");
+                loadBooleanSetting(stmt, Settings.Secure.WAKE_GESTURE_ENABLED,
+                        R.bool.def_wake_gesture_enabled);
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+                if (stmt != null) stmt.close();
+            }
+            upgradeVersion = 104;
+        }
+
+        if (upgradeVersion < 105) {
+            if (mUserHandle == UserHandle.USER_OWNER) {
+                db.beginTransaction();
+                SQLiteStatement stmt = null;
+                try {
+                    stmt = db.compileStatement("INSERT OR IGNORE INTO global(name,value)"
+                            + " VALUES(?,?);");
+                    loadBooleanSetting(stmt, Settings.Global.GUEST_USER_ENABLED,
+                            R.bool.def_guest_user_enabled);
+                    db.setTransactionSuccessful();
+                } finally {
+                    db.endTransaction();
+                    if (stmt != null) stmt.close();
+                }
+            }
+            upgradeVersion = 105;
+        }
+
         // *** Remember to update DATABASE_VERSION above!
 
         if (upgradeVersion != currentVersion) {
@@ -2222,6 +2257,9 @@
             loadBooleanSetting(stmt, Settings.Secure.INSTALL_NON_MARKET_APPS,
                     R.bool.def_install_non_market_apps);
 
+            loadBooleanSetting(stmt, Settings.Secure.WAKE_GESTURE_ENABLED,
+                    R.bool.def_wake_gesture_enabled);
+
         } finally {
             if (stmt != null) stmt.close();
         }
@@ -2390,6 +2428,8 @@
 
             loadSetting(stmt, Settings.Global.DEVICE_NAME, getDefaultDeviceName());
 
+            loadBooleanSetting(stmt, Settings.Global.GUEST_USER_ENABLED,
+                    R.bool.def_guest_user_enabled);
             // --- New global settings start here
         } finally {
             if (stmt != null) stmt.close();
diff --git a/packages/Shell/res/values-de/strings.xml b/packages/Shell/res/values-de/strings.xml
index 99522b1..34481ba 100644
--- a/packages/Shell/res/values-de/strings.xml
+++ b/packages/Shell/res/values-de/strings.xml
@@ -18,7 +18,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="3701846017049540910">"Shell"</string>
     <string name="bugreport_finished_title" msgid="2293711546892863898">"Fehlerbericht erfasst"</string>
-    <string name="bugreport_finished_text" msgid="3559904746859400732">"Berühren, um Fehlerbericht zu teilen"</string>
+    <string name="bugreport_finished_text" msgid="3559904746859400732">"Tippen, um Fehlerbericht zu teilen"</string>
     <string name="bugreport_confirm" msgid="5130698467795669780">"Fehlerberichte enthalten Daten aus verschiedenen Protokolldateien des Systems, darunter auch personenbezogene und private Daten. Teilen Sie Fehlerberichte nur mit Apps und Personen, denen Sie vertrauen."</string>
     <string name="bugreport_confirm_repeat" msgid="4926842460688645058">"Diese Nachricht nächstes Mal zeigen"</string>
 </resources>
diff --git a/packages/Shell/res/values-fr/strings.xml b/packages/Shell/res/values-fr/strings.xml
index 1da6f1f..12f5e88 100644
--- a/packages/Shell/res/values-fr/strings.xml
+++ b/packages/Shell/res/values-fr/strings.xml
@@ -18,7 +18,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="3701846017049540910">"Shell"</string>
     <string name="bugreport_finished_title" msgid="2293711546892863898">"Rapport de bug enregistré"</string>
-    <string name="bugreport_finished_text" msgid="3559904746859400732">"Appuyer ici pour partager votre rapport de bug"</string>
+    <string name="bugreport_finished_text" msgid="3559904746859400732">"Appuyez ici pour partager le rapport de bug"</string>
     <string name="bugreport_confirm" msgid="5130698467795669780">"Les rapports de bug contiennent des données des fichiers journaux du système, y compris des informations personnelles et privées. Ne partagez les rapports de bug qu\'avec les applications et les personnes que vous estimez fiables."</string>
     <string name="bugreport_confirm_repeat" msgid="4926842460688645058">"Afficher ce message la prochaine fois"</string>
 </resources>
diff --git a/packages/Shell/res/values-ja/strings.xml b/packages/Shell/res/values-ja/strings.xml
index 88b9c14..db34041 100644
--- a/packages/Shell/res/values-ja/strings.xml
+++ b/packages/Shell/res/values-ja/strings.xml
@@ -18,7 +18,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="3701846017049540910">"シェル"</string>
     <string name="bugreport_finished_title" msgid="2293711546892863898">"バグレポートが記録されました"</string>
-    <string name="bugreport_finished_text" msgid="3559904746859400732">"タップしてバグレポートを共有する"</string>
+    <string name="bugreport_finished_text" msgid="3559904746859400732">"バグレポートを共有するにはタップします"</string>
     <string name="bugreport_confirm" msgid="5130698467795669780">"バグレポートには、個人の非公開情報など、システムのさまざまなログファイルのデータが含まれます。共有する場合は信頼するアプリとユーザーのみを選択してください。"</string>
     <string name="bugreport_confirm_repeat" msgid="4926842460688645058">"このメッセージを次回も表示する"</string>
 </resources>
diff --git a/packages/SystemUI/res/anim/recents_from_app_enter.xml b/packages/SystemUI/res/anim/recents_from_app_enter.xml
new file mode 100644
index 0000000..6abe8b3
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_from_app_enter.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false"
+     android:zAdjustment="top">
+  <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
+         android:fillEnabled="true"
+         android:fillBefore="true" android:fillAfter="true"
+         android:interpolator="@android:interpolator/fast_out_slow_in"
+         android:duration="0"/>
+</set>
diff --git a/packages/SystemUI/res/anim/recents_from_app_exit.xml b/packages/SystemUI/res/anim/recents_from_app_exit.xml
new file mode 100644
index 0000000..1447a5a
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_from_app_exit.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false"
+     android:zAdjustment="normal">
+
+    <!-- Animate the view out only after recents is visible -->
+    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+           android:fillEnabled="true"
+           android:fillBefore="true" android:fillAfter="true"
+           android:interpolator="@android:interpolator/fast_out_slow_in"
+           android:duration="1"/>
+</set>
diff --git a/packages/SystemUI/res/anim/recents_from_launcher_enter.xml b/packages/SystemUI/res/anim/recents_from_launcher_enter.xml
index 4bd7e82..bac8cb6 100644
--- a/packages/SystemUI/res/anim/recents_from_launcher_enter.xml
+++ b/packages/SystemUI/res/anim/recents_from_launcher_enter.xml
@@ -20,9 +20,9 @@
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:shareInterpolator="false"
      android:zAdjustment="top">
-  <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+  <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
          android:fillEnabled="true"
          android:fillBefore="true" android:fillAfter="true"
-         android:interpolator="@android:interpolator/accelerate_cubic"
-         android:duration="250"/>
+         android:interpolator="@android:interpolator/fast_out_slow_in"
+         android:duration="100"/>
 </set>
diff --git a/packages/SystemUI/res/anim/recents_from_launcher_exit.xml b/packages/SystemUI/res/anim/recents_from_launcher_exit.xml
index becc9d0..b0f8807 100644
--- a/packages/SystemUI/res/anim/recents_from_launcher_exit.xml
+++ b/packages/SystemUI/res/anim/recents_from_launcher_exit.xml
@@ -23,6 +23,6 @@
   <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
          android:fillEnabled="true"
          android:fillBefore="true" android:fillAfter="true"
-         android:interpolator="@android:interpolator/decelerate_cubic"
-         android:duration="250"/>
+         android:interpolator="@android:interpolator/fast_out_slow_in"
+         android:duration="100"/>
 </set>
diff --git a/packages/SystemUI/res/anim/recents_from_unknown_enter.xml b/packages/SystemUI/res/anim/recents_from_unknown_enter.xml
new file mode 100644
index 0000000..f68a143
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_from_unknown_enter.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false"
+     android:zAdjustment="top">
+  <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+         android:fillEnabled="true"
+         android:fillBefore="true" android:fillAfter="true"
+         android:interpolator="@android:interpolator/fast_out_slow_in"
+         android:duration="200"/>
+</set>
diff --git a/packages/SystemUI/res/anim/recents_from_unknown_exit.xml b/packages/SystemUI/res/anim/recents_from_unknown_exit.xml
new file mode 100644
index 0000000..31cf26a
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_from_unknown_exit.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false"
+     android:zAdjustment="normal">
+  <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+         android:fillEnabled="true"
+         android:fillBefore="true" android:fillAfter="true"
+         android:interpolator="@android:interpolator/fast_out_slow_in"
+         android:duration="200"/>
+</set>
diff --git a/packages/SystemUI/res/anim/recents_to_launcher_enter.xml b/packages/SystemUI/res/anim/recents_to_launcher_enter.xml
new file mode 100644
index 0000000..2857c04
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_to_launcher_enter.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false"
+     android:zAdjustment="normal">
+  <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+         android:fillEnabled="true"
+         android:fillBefore="true" android:fillAfter="true"
+         android:interpolator="@android:interpolator/fast_out_slow_in"
+         android:duration="150"/>
+</set>
diff --git a/packages/SystemUI/res/anim/recents_to_launcher_exit.xml b/packages/SystemUI/res/anim/recents_to_launcher_exit.xml
new file mode 100644
index 0000000..1139e72
--- /dev/null
+++ b/packages/SystemUI/res/anim/recents_to_launcher_exit.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false"
+     android:zAdjustment="top">
+  <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+         android:fillEnabled="true"
+         android:fillBefore="true" android:fillAfter="true"
+         android:interpolator="@android:interpolator/fast_out_slow_in"
+         android:duration="150"/>
+</set>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
index 4028ec3..d24d21e 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
@@ -35,12 +35,65 @@
         android:background="@drawable/notification_header_bg"
         android:clickable="true"
         />
+
+    <View android:id="@+id/header_spacer"
+        android:layout_height="8dp"
+        android:layout_width="0dp" />
+
+    <TextView
+        android:id="@+id/header_emergency_calls_only"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_below="@id/header_spacer"
+        android:paddingTop="12dp"
+        android:paddingStart="16dp"
+        android:paddingEnd="16dp"
+        android:visibility="gone"
+        android:textAppearance="@style/TextAppearance.StatusBar.Expanded.EmergencyCallsOnly"
+        android:text="@*android:string/emergency_calls_only" />
+
+    <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
+        android:layout_width="40dp"
+        android:layout_height="@dimen/status_bar_header_height"
+        android:layout_alignParentEnd="true"
+        android:background="@null"
+        android:scaleType="centerInside"
+        android:padding="8dp" />
+
+    <ImageButton android:id="@+id/settings_button"
+        style="@android:style/Widget.Material.Button.Borderless"
+        android:layout_toStartOf="@id/multi_user_switch"
+        android:layout_width="56dp"
+        android:layout_height="@dimen/status_bar_header_height"
+        android:src="@drawable/ic_settings_24dp"
+        android:contentDescription="@string/accessibility_desc_quick_settings"/>
+
+    <FrameLayout android:id="@+id/system_icons_container"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/status_bar_header_height"
+        android:layout_toStartOf="@id/multi_user_switch"
+        android:layout_marginEnd="4dp"
+        android:layout_marginStart="16dp"
+        />
+
+    <TextView
+        android:id="@+id/header_charging_info"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toEndOf="@id/system_icons_container"
+        android:layout_below="@id/header_spacer"
+        android:paddingTop="12dp"
+        android:paddingEnd="16dp"
+        android:paddingStart="4dp"
+        style="@style/TextAppearance.StatusBar.Expanded.ChargingInfo"/>
+
     <RelativeLayout
         android:id="@+id/datetime"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="start"
-        android:paddingTop="16dp"
+        android:layout_below="@id/header_emergency_calls_only"
+        android:paddingTop="8dp"
         android:paddingBottom="16dp"
         android:paddingStart="16dp"
         android:paddingEnd="16dp"
@@ -65,36 +118,11 @@
             />
     </RelativeLayout>
 
-    <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
-        android:layout_width="40dp"
-        android:layout_height="@dimen/status_bar_header_height"
-        android:layout_alignParentEnd="true"
-        android:background="@null"
-        android:scaleType="centerInside"
-        android:padding="8dp"
-        />
-
-    <ImageButton android:id="@+id/settings_button"
-        style="@android:style/Widget.Material.Button.Borderless"
-        android:layout_toStartOf="@id/multi_user_switch"
-        android:layout_width="48dp"
-        android:layout_height="@dimen/status_bar_header_height"
-        android:layout_marginStart="8dp"
-        android:src="@drawable/ic_settings_24dp"
-        android:contentDescription="@string/accessibility_desc_quick_settings"/>
-
-    <FrameLayout android:id="@+id/system_icons_container"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/status_bar_header_height"
-        android:layout_toStartOf="@id/multi_user_switch"
-        android:layout_marginEnd="2dp"
-        />
-
     <com.android.keyguard.CarrierText
         android:id="@+id/keyguard_carrier_text"
         android:layout_width="match_parent"
         android:layout_height="@dimen/status_bar_header_height_keyguard"
-        android:layout_marginLeft="16dp"
+        android:layout_marginLeft="8dp"
         android:layout_toStartOf="@id/system_icons_container"
         android:gravity="center_vertical"
         android:ellipsize="marquee"
@@ -106,6 +134,8 @@
         layout="@layout/quick_settings_brightness_dialog"
         android:id="@+id/brightness_container"
         android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
         />
 
     <TextView
diff --git a/packages/SystemUI/res/layout/user_switcher_item.xml b/packages/SystemUI/res/layout/user_switcher_item.xml
index 43a85e7..8df2f5a 100644
--- a/packages/SystemUI/res/layout/user_switcher_item.xml
+++ b/packages/SystemUI/res/layout/user_switcher_item.xml
@@ -21,10 +21,12 @@
         android:layout_width="match_parent"
         android:layout_height="64dp"
         android:orientation="horizontal"
+        android:gravity="center_vertical"
         tools:context=".settings.UserSwitcherDialog">
     <ImageView
-            android:layout_width="64dp"
-            android:layout_height="match_parent"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginStart="4dp"
             android:id="@+id/user_picture"
             tools:src="@drawable/dessert_zombiegingerbread"/>
     <TextView
@@ -37,4 +39,11 @@
             android:gravity="center_vertical"
             tools:text="Hiroshi Lockheimer"
             />
+    <ImageView
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginEnd="4dp"
+            android:src="@*android:drawable/ic_menu_delete"
+            android:id="@+id/user_delete"
+            android:background="?android:attr/selectableItemBackground"/>
 </LinearLayout>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1ef5bcd..b39fa84 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -113,12 +113,16 @@
     <!-- The min animation duration for animating views that are newly visible. -->
     <integer name="recents_filter_animate_new_views_min_duration">125</integer>
     <!-- The min animation duration for animating the task bar in. -->
-    <integer name="recents_animate_task_bar_enter_duration">250</integer>
+    <integer name="recents_animate_task_bar_enter_duration">275</integer>
     <!-- The animation delay for animating the first task in. This should roughly be the animation
      duration of the transition in to recents. -->
     <integer name="recents_animate_task_bar_enter_delay">225</integer>
     <!-- The min animation duration for animating the task bar out. -->
     <integer name="recents_animate_task_bar_exit_duration">125</integer>
+    <!-- The min animation duration for animating the task in when transitioning from home. -->
+    <integer name="recents_animate_task_enter_from_home_duration">325</integer>
+    <!-- The animation stagger to apply to each task animation when transitioning from home. -->
+    <integer name="recents_animate_task_enter_from_home_delay">16</integer>
     <!-- The min animation duration for animating the nav bar scrim in. -->
     <integer name="recents_nav_bar_scrim_enter_duration">400</integer>
     <!-- The animation duration for animating the removal of a task view. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 836cf82..5ffe3b3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -235,9 +235,6 @@
     <!-- The amount of highlight to make on each task view. -->
     <dimen name="recents_task_view_highlight">1dp</dimen>
 
-    <!-- The amount of space a user has to scroll to dismiss any info panes. -->
-    <dimen name="recents_task_stack_scroll_dismiss_info_pane_distance">50dp</dimen>
-
     <!-- The height of the search bar space. -->
     <dimen name="recents_search_bar_space_height">64dp</dimen>
 
@@ -333,9 +330,6 @@
          phone hints. -->
     <dimen name="edge_tap_area_width">48dp</dimen>
 
-    <!-- end margin for multi user switch in expanded quick settings -->
-    <dimen name="multi_user_switch_expanded_margin">8dp</dimen>
-
     <!-- the distance the panel moves up when starting the up motion on Keyguard -->
     <dimen name="keyguard_panel_move_up_distance">100dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 260f59c..e5499ee 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -549,6 +549,18 @@
     <string name="recents_search_bar_label">search</string>
 
 
+    <!-- Expanded Status Bar Header: Battery Charged [CHAR LIMIT=40] -->
+    <string name="expanded_header_battery_charged">Charged</string>
+
+    <!-- Expanded Status Bar Header: Charging, no known time [CHAR LIMIT=40] -->
+    <string name="expanded_header_battery_charging">Charging</string>
+
+    <!-- Expanded Status Bar Header: Charging, showing time left until charged [CHAR LIMIT=40] -->
+    <string name="expanded_header_battery_charging_with_time"><xliff:g id="charging_time" example="2 hrs 25 min">%s</xliff:g> until full</string>
+
+    <!-- Expanded Status Bar Header: Not charging [CHAR LIMIT=40] -->
+    <string name="expanded_header_battery_not_charging">Not charging</string>
+
     <!-- Glyph to be overlaid atop the battery when the level is extremely low. Do not translate. -->
     <string name="battery_meter_very_low_overlay_symbol">!</string>
 
@@ -602,6 +614,13 @@
     <!-- Indication on the keyguard that is shown when the device is charging. [CHAR LIMIT=40]-->
     <string name="keyguard_indication_charging_time">Charging (<xliff:g id="charging_time_left" example="4 hours and 2 minutes">%s</xliff:g> until full)</string>
 
+    <!-- Related to user switcher --><skip/>
+    <!-- Name for the guest user -->
+    <string name="guest_nickname">Guest</string>
+
+    <!-- Label for adding a new guest -->
+    <string name="guest_new_guest">+ Guest</string>
+
     <!-- Zen mode condition: time duration in minutes. [CHAR LIMIT=NONE] -->
     <plurals name="zen_mode_duration_minutes">
         <item quantity="one">For one minute</item>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index b0b018d..43560a3 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -70,17 +70,29 @@
     <style name="TextAppearance.StatusBar.Expanded" parent="@*android:style/TextAppearance.StatusBar" />
 
     <style name="TextAppearance.StatusBar.Expanded.Clock">
-        <item name="android:textSize">18dp</item>
+        <item name="android:textSize">16dp</item>
         <item name="android:textStyle">normal</item>
         <item name="android:textColor">#ffffff</item>
     </style>
 
     <style name="TextAppearance.StatusBar.Expanded.Date">
+        <item name="android:textSize">14dp</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">#99ffffff</item>
+    </style>
+
+    <style name="TextAppearance.StatusBar.Expanded.AboveDateTime">
         <item name="android:textSize">12dp</item>
         <item name="android:textStyle">normal</item>
-        <item name="android:textColor">#afb3b6</item>
+        <item name="android:textColor">#99ffffff</item>
     </style>
 
+    <style name="TextAppearance.StatusBar.Expanded.EmergencyCallsOnly"
+           parent="TextAppearance.StatusBar.Expanded.AboveDateTime" />
+
+    <style name="TextAppearance.StatusBar.Expanded.ChargingInfo"
+            parent="TextAppearance.StatusBar.Expanded.AboveDateTime" />
+
     <style name="TextAppearance.StatusBar.Expanded.Network" parent="@style/TextAppearance.StatusBar.Expanded.Date">
         <item name="android:textColor">#999999</item>
 	</style>
diff --git a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java
index 4147155..14392b4 100644
--- a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java
@@ -507,7 +507,6 @@
                     | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                     | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                     | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                     | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
             );
         }
diff --git a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
index 7c85712..9650435 100644
--- a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
@@ -20,9 +20,14 @@
 import android.view.View;
 
 public interface RecentsComponent {
+    public interface Callbacks {
+        public void onVisibilityChanged(boolean visible);
+    }
+
     void showRecents(boolean triggeredFromAltTab, View statusBarView);
     void hideRecents(boolean triggeredFromAltTab);
     void toggleRecents(Display display, int layoutDirection, View statusBarView);
     void preloadRecents();
     void cancelPreloadingRecents();
+    void setCallback(Callbacks cb);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c006c88..ffd76a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -59,8 +59,6 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.MultiUserAvatarCache;
 import com.android.keyguard.ViewMediatorCallback;
-import com.android.keyguard.analytics.KeyguardAnalytics;
-import com.android.keyguard.analytics.Session;
 import com.android.systemui.SystemUI;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 import com.android.systemui.statusbar.phone.ScrimController;
@@ -70,7 +68,6 @@
 import java.io.File;
 
 import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
-import static com.android.keyguard.analytics.KeyguardAnalytics.SessionTypeAdapter;
 
 
 /**
@@ -117,7 +114,6 @@
 public class KeyguardViewMediator extends SystemUI {
     private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;
     final static boolean DEBUG = false;
-    private static final boolean ENABLE_ANALYTICS = false;
     private final static boolean DBG_WAKE = false;
 
     private final static String TAG = "KeyguardViewMediator";
@@ -199,8 +195,6 @@
 
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
-    private KeyguardAnalytics mKeyguardAnalytics;
-
     // these are protected by synchronized (this)
 
     /**
@@ -469,22 +463,6 @@
                 mViewMediatorCallback, mLockPatternUtils);
         final ContentResolver cr = mContext.getContentResolver();
 
-        if (ENABLE_ANALYTICS && !LockPatternUtils.isSafeModeEnabled() &&
-                Settings.Secure.getInt(cr, KEYGUARD_ANALYTICS_SETTING, 0) == 1) {
-            mKeyguardAnalytics = new KeyguardAnalytics(mContext, new SessionTypeAdapter() {
-
-                @Override
-                public int getSessionType() {
-                    return mLockPatternUtils.isSecure() && !mUpdateMonitor.getUserHasTrust(
-                            mLockPatternUtils.getCurrentUser())
-                            ? Session.TYPE_KEYGUARD_SECURE
-                            : Session.TYPE_KEYGUARD_INSECURE;
-                }
-            }, new File(mContext.getCacheDir(), "keyguard_analytics.bin"));
-        } else {
-            mKeyguardAnalytics = null;
-        }
-
         mScreenOn = mPM.isScreenOn();
 
         mLockSounds = new SoundPool(1, AudioManager.STREAM_SYSTEM, 0);
@@ -585,9 +563,6 @@
             } else {
                 doKeyguardLocked(null);
             }
-            if (ENABLE_ANALYTICS && mKeyguardAnalytics != null) {
-                mKeyguardAnalytics.getCallback().onScreenOff();
-            }
         }
         KeyguardUpdateMonitor.getInstance(mContext).dispatchScreenTurndOff(why);
     }
@@ -830,9 +805,6 @@
                 updateActivityLockScreenState();
                 adjustStatusBarLocked();
             }
-            if (ENABLE_ANALYTICS && mKeyguardAnalytics != null) {
-                mKeyguardAnalytics.getCallback().onSetOccluded(isOccluded);
-            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
index 116d755d..e03c01c 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
@@ -273,6 +273,13 @@
         }
     }
 
+    @Override
+    public void setCallback(Callbacks cb) {
+        if (mUseAlternateRecents) {
+            mAlternateRecents.setRecentsComponentCallback(cb);
+        }
+    }
+
     /**
      * Send broadcast only if BOOT_COMPLETED
      */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index 2f6d58f..8861752 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -27,7 +27,6 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
@@ -35,16 +34,13 @@
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.UserHandle;
-import android.util.DisplayMetrics;
-import android.view.Display;
-import android.view.Surface;
-import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
 import com.android.systemui.R;
+import com.android.systemui.RecentsComponent;
 
+import java.lang.ref.WeakReference;
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -129,8 +125,10 @@
     final public static int MSG_TOGGLE_RECENTS = 6;
     final public static int MSG_START_ENTER_ANIMATION = 7;
 
-    final public static String EXTRA_ANIMATING_WITH_THUMBNAIL = "recents.animatingWithThumbnail";
-    final public static String EXTRA_FROM_ALT_TAB = "recents.triggeredFromAltTab";
+    final public static String EXTRA_FROM_HOME = "recents.triggeredOverHome";
+    final public static String EXTRA_FROM_APP_THUMBNAIL = "recents.animatingWithThumbnail";
+    final public static String EXTRA_FROM_APP_FULL_SCREENSHOT = "recents.thumbnail";
+    final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "recents.triggeredFromAltTab";
     final public static String KEY_CONFIGURATION_DATA = "recents.data.updateForConfiguration";
     final public static String KEY_WINDOW_RECT = "recents.windowRect";
     final public static String KEY_SYSTEM_INSETS = "recents.systemInsets";
@@ -138,7 +136,6 @@
     final public static String KEY_TWO_TASK_STACK_RECT = "recents.twoCountTaskRect";
     final public static String KEY_MULTIPLE_TASK_STACK_RECT = "recents.multipleCountTaskRect";
 
-
     final static int sMinToggleDelay = 425;
 
     final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
@@ -146,6 +143,9 @@
     final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
     final static String sRecentsService = "com.android.systemui.recents.RecentsService";
 
+    static Bitmap sLastScreenshot;
+    static RecentsComponent.Callbacks sRecentsComponentCallbacks;
+
     Context mContext;
     SystemServicesProxy mSystemServicesProxy;
 
@@ -213,15 +213,19 @@
         if (Console.Enabled) {
             Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|hideRecents]");
         }
+
         if (mServiceIsBound && mBootCompleted) {
-            // Notify recents to close it
-            try {
-                Bundle data = new Bundle();
-                Message msg = Message.obtain(null, MSG_HIDE_RECENTS, triggeredFromAltTab ? 1 : 0, 0);
-                msg.setData(data);
-                mService.send(msg);
-            } catch (RemoteException re) {
-                re.printStackTrace();
+            if (isRecentsTopMost(null)) {
+                // Notify recents to close it
+                try {
+                    Bundle data = new Bundle();
+                    Message msg = Message.obtain(null, MSG_HIDE_RECENTS,
+                            triggeredFromAltTab ? 1 : 0, 0);
+                    msg.setData(data);
+                    mService.send(msg);
+                } catch (RemoteException re) {
+                    re.printStackTrace();
+                }
             }
         }
     }
@@ -343,80 +347,6 @@
         }
     }
 
-    /** Converts from the device rotation to the degree */
-    float getDegreesForRotation(int value) {
-        switch (value) {
-            case Surface.ROTATION_90:
-                return 360f - 90f;
-            case Surface.ROTATION_180:
-                return 360f - 180f;
-            case Surface.ROTATION_270:
-                return 360f - 270f;
-        }
-        return 0f;
-    }
-
-    /** Takes a screenshot of the surface */
-    Bitmap takeScreenshot(Display display) {
-        DisplayMetrics dm = new DisplayMetrics();
-        display.getRealMetrics(dm);
-        float[] dims = {dm.widthPixels, dm.heightPixels};
-        float degrees = getDegreesForRotation(display.getRotation());
-        boolean requiresRotation = (degrees > 0);
-        if (requiresRotation) {
-            // Get the dimensions of the device in its native orientation
-            Matrix m = new Matrix();
-            m.preRotate(-degrees);
-            m.mapPoints(dims);
-            dims[0] = Math.abs(dims[0]);
-            dims[1] = Math.abs(dims[1]);
-        }
-        return SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
-    }
-
-    /** Creates the activity options for a thumbnail transition. */
-    ActivityOptions getThumbnailTransitionActivityOptions(Rect taskRect) {
-        // Loading from thumbnail
-        Bitmap thumbnail;
-        Bitmap firstThumbnail = loadFirstTaskThumbnail();
-        if (firstThumbnail != null) {
-            // Create the thumbnail
-            thumbnail = Bitmap.createBitmap(taskRect.width(), taskRect.height(),
-                    Bitmap.Config.ARGB_8888);
-            int size = Math.min(firstThumbnail.getWidth(), firstThumbnail.getHeight());
-            Canvas c = new Canvas(thumbnail);
-            c.drawBitmap(firstThumbnail, new Rect(0, 0, size, size),
-                    new Rect(0, 0, taskRect.width(), taskRect.height()), null);
-            c.setBitmap(null);
-            // Recycle the old thumbnail
-            firstThumbnail.recycle();
-        } else {
-            // Load the thumbnail from the screenshot if can't get one from the system
-            WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
-            Display display = wm.getDefaultDisplay();
-            Bitmap screenshot = takeScreenshot(display);
-            if (screenshot != null) {
-                Resources res = mContext.getResources();
-                int size = Math.min(screenshot.getWidth(), screenshot.getHeight());
-                int statusBarHeight = res.getDimensionPixelSize(
-                        com.android.internal.R.dimen.status_bar_height);
-                thumbnail = Bitmap.createBitmap(taskRect.width(), taskRect.height(),
-                        Bitmap.Config.ARGB_8888);
-                Canvas c = new Canvas(thumbnail);
-                c.drawBitmap(screenshot, new Rect(0, statusBarHeight, size, statusBarHeight +
-                        size), new Rect(0, 0, taskRect.width(), taskRect.height()), null);
-                c.setBitmap(null);
-                // Recycle the temporary screenshot
-                screenshot.recycle();
-            } else {
-                return null;
-            }
-        }
-
-        return ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView, thumbnail,
-                taskRect.left, taskRect.top, this);
-    }
-
     /** Returns whether the recents is currently running */
     boolean isRecentsTopMost(AtomicBoolean isHomeTopMost) {
         SystemServicesProxy ssp = mSystemServicesProxy;
@@ -462,10 +392,12 @@
                 mService.send(msg);
 
                 // Time this path
-                Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
-                        Constants.Log.App.TimeRecentsStartupKey, "sendToggleRecents");
-                Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
-                        Constants.Log.App.TimeRecentsLaunchKey, "sendToggleRecents");
+                if (Console.Enabled) {
+                    Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
+                            Constants.Log.App.TimeRecentsStartupKey, "sendToggleRecents");
+                    Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
+                            Constants.Log.App.TimeRecentsLaunchKey, "sendToggleRecents");
+                }
             } catch (RemoteException re) {
                 re.printStackTrace();
             }
@@ -486,6 +418,68 @@
         }
     }
 
+    /**
+     * Creates the activity options for a unknown state->recents transition.
+     */
+    ActivityOptions getUnknownTransitionActivityOptions() {
+        // Reset the last screenshot
+        consumeLastScreenshot();
+        return ActivityOptions.makeCustomAnimation(mContext,
+                R.anim.recents_from_unknown_enter,
+                R.anim.recents_from_unknown_exit, mHandler, this);
+    }
+
+    /**
+     * Creates the activity options for a home->recents transition.
+     */
+    ActivityOptions getHomeTransitionActivityOptions() {
+        // Reset the last screenshot
+        consumeLastScreenshot();
+        return ActivityOptions.makeCustomAnimation(mContext,
+                R.anim.recents_from_launcher_enter,
+                R.anim.recents_from_launcher_exit, mHandler, this);
+    }
+
+    /**
+     * Creates the activity options for an app->recents transition.  If this method sets the static
+     * screenshot, then we will use that for the transition.
+     */
+    ActivityOptions getThumbnailTransitionActivityOptions(Rect taskRect) {
+        // Recycle the last screenshot
+        consumeLastScreenshot();
+
+        // Take the full screenshot
+        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
+            sLastScreenshot = mSystemServicesProxy.takeScreenshot();
+            if (sLastScreenshot != null) {
+                return ActivityOptions.makeCustomAnimation(mContext,
+                        R.anim.recents_from_app_enter,
+                        R.anim.recents_from_app_exit, mHandler, this);
+            }
+        }
+
+        // If the screenshot fails, then load the first task thumbnail and use that
+        Bitmap firstThumbnail = loadFirstTaskThumbnail();
+        if (firstThumbnail != null) {
+            // Create the new thumbnail for the animation down
+            // XXX: We should find a way to optimize this so we don't need to create a new bitmap
+            Bitmap thumbnail = Bitmap.createBitmap(taskRect.width(), taskRect.height(),
+                    Bitmap.Config.ARGB_8888);
+            int size = Math.min(firstThumbnail.getWidth(), firstThumbnail.getHeight());
+            Canvas c = new Canvas(thumbnail);
+            c.drawBitmap(firstThumbnail, new Rect(0, 0, size, size),
+                    new Rect(0, 0, taskRect.width(), taskRect.height()), null);
+            c.setBitmap(null);
+            // Recycle the old thumbnail
+            firstThumbnail.recycle();
+            return ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView,
+                    thumbnail, taskRect.left, taskRect.top, this);
+        }
+
+        // If both the screenshot and thumbnail fails, then just fall back to the default transition
+        return getUnknownTransitionActivityOptions();
+    }
+
     /** Starts the recents activity */
     void startRecentsActivity(boolean isTopTaskHome) {
         // If Recents is not the front-most activity and we should animate into it.  If
@@ -503,7 +497,11 @@
             // Try starting with a thumbnail transition
             ActivityOptions opts = getThumbnailTransitionActivityOptions(taskRect);
             if (opts != null) {
-                startAlternateRecentsActivity(opts, true);
+                if (sLastScreenshot != null) {
+                    startAlternateRecentsActivity(opts, EXTRA_FROM_APP_FULL_SCREENSHOT);
+                } else {
+                    startAlternateRecentsActivity(opts, EXTRA_FROM_APP_THUMBNAIL);
+                }
             } else {
                 // Fall through below to the non-thumbnail transition
                 useThumbnailTransition = false;
@@ -512,25 +510,33 @@
 
         // If there is no thumbnail transition, then just use a generic transition
         if (!useThumbnailTransition) {
-            ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
-                    R.anim.recents_from_launcher_enter,
-                    R.anim.recents_from_launcher_exit, mHandler, this);
-            startAlternateRecentsActivity(opts, false);
+            if (Constants.DebugFlags.App.EnableHomeTransition) {
+                ActivityOptions opts = getHomeTransitionActivityOptions();
+                startAlternateRecentsActivity(opts, EXTRA_FROM_HOME);
+            } else {
+                ActivityOptions opts = getUnknownTransitionActivityOptions();
+                startAlternateRecentsActivity(opts, null);
+            }
         }
 
-        Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
-                Constants.Log.App.TimeRecentsStartupKey, "startRecentsActivity");
+        if (Console.Enabled) {
+            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
+                    Constants.Log.App.TimeRecentsStartupKey, "startRecentsActivity");
+        }
         mLastToggleTime = System.currentTimeMillis();
     }
 
     /** Starts the recents activity */
-    void startAlternateRecentsActivity(ActivityOptions opts, boolean animatingWithThumbnail) {
+    void startAlternateRecentsActivity(ActivityOptions opts, String extraFlag) {
         Intent intent = new Intent(sToggleRecentsAction);
         intent.setClassName(sRecentsPackage, sRecentsActivity);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-        intent.putExtra(EXTRA_ANIMATING_WITH_THUMBNAIL, animatingWithThumbnail);
-        intent.putExtra(EXTRA_FROM_ALT_TAB, mTriggeredFromAltTab);
+                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+        if (extraFlag != null) {
+            intent.putExtra(extraFlag, true);
+        }
+        intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, mTriggeredFromAltTab);
         if (opts != null) {
             mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
                     UserHandle.USER_CURRENT));
@@ -539,6 +545,30 @@
         }
     }
 
+    /** Returns the last screenshot taken, this will be called by the RecentsActivity. */
+    public static Bitmap getLastScreenshot() {
+        return sLastScreenshot;
+    }
+
+    /** Recycles the last screenshot taken, this will be called by the RecentsActivity. */
+    public static void consumeLastScreenshot() {
+        if (sLastScreenshot != null) {
+            sLastScreenshot.recycle();
+            sLastScreenshot = null;
+        }
+    }
+
+    /** Sets the RecentsComponent callbacks. */
+    public void setRecentsComponentCallback(RecentsComponent.Callbacks cb) {
+        sRecentsComponentCallbacks = cb;
+    }
+
+    /** Notifies the callbacks that the visibility of Recents has changed. */
+    public static void notifyVisibilityChanged(boolean visible) {
+        if (sRecentsComponentCallbacks != null) {
+            sRecentsComponentCallbacks.onVisibilityChanged(visible);
+        }
+    }
 
     /**** OnAnimationStartedListener Implementation ****/
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 147ff62..cd4d206 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -25,6 +25,10 @@
         public static final boolean Verbose = false;
 
         public static class App {
+            // Enables the home->Recents transition
+            public static final boolean EnableHomeTransition = false;
+            // Enables the screenshot app->Recents transition
+            public static final boolean EnableScreenshotAppTransition = false;
             // Enables the filtering of tasks according to their grouping
             public static final boolean EnableTaskFiltering = false;
             // Enables clipping of tasks against each other
@@ -52,8 +56,11 @@
         public static class App {
             public static final String TimeRecentsStartupKey = "startup";
             public static final String TimeRecentsLaunchKey = "launchTask";
-            public static final boolean TimeRecentsStartup = false;
-            public static final boolean TimeRecentsLaunchTask = false;
+            public static final String TimeRecentsScreenshotTransitionKey = "screenshot";
+            public static final boolean TimeRecentsStartup = true;
+            public static final boolean TimeRecentsLaunchTask = true;
+            public static final boolean TimeRecentsScreenshotTransition = true;
+
 
             public static final boolean RecentsComponent = false;
             public static final boolean TaskDataLoader = false;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 96344d5..f9c219b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -22,11 +22,9 @@
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.res.Configuration;
 import android.os.Bundle;
 import android.util.Pair;
 import android.view.Gravity;
@@ -34,17 +32,17 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.WindowManager;
 import android.widget.FrameLayout;
 import com.android.systemui.R;
 import com.android.systemui.recents.model.SpaceNode;
 import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.FullScreenTransitionView;
 import com.android.systemui.recents.views.RecentsView;
+import com.android.systemui.recents.views.ViewAnimation;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
-import java.util.Set;
 
 /** Our special app widget host */
 class RecentsAppWidgetHost extends AppWidgetHost {
@@ -68,11 +66,16 @@
 
 /* Activity */
 public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
-        RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks {
+        RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks,
+        FullScreenTransitionView.FullScreenTransitionViewCallbacks {
+
     FrameLayout mContainerView;
     RecentsView mRecentsView;
     View mEmptyView;
     View mNavBarScrimView;
+    FullScreenTransitionView mFullScreenshotView;
+
+    RecentsConfiguration mConfig;
 
     AppWidgetHost mAppWidgetHost;
     AppWidgetProviderInfo mSearchAppWidgetInfo;
@@ -108,8 +111,15 @@
                     // Dismiss recents, launching the focused task
                     dismissRecentsIfVisible();
                 } else {
-                    // Otherwise, just finish the activity without launching any other activities
-                    finish();
+                    // If we are mid-animation into Recents, then reverse it and finish
+                    if (mFullScreenshotView == null ||
+                            !mFullScreenshotView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
+                        // Otherwise, just finish the activity without launching any other activities
+                        ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(context,
+                                null, mFinishRunnable, null);
+                        mRecentsView.startOnExitAnimation(
+                                new ViewAnimation.TaskViewExitContext(exitTrigger));
+                    }
                 }
             } else if (action.equals(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
                 // Try and unfilter and filtered stacks
@@ -119,7 +129,9 @@
                 }
             } else if (action.equals(RecentsService.ACTION_START_ENTER_ANIMATION)) {
                 // Try and start the enter animation (or restart it on configuration changed)
-                mRecentsView.startOnEnterAnimation();
+                mRecentsView.startOnEnterAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenshotView));
+                // Call our callback
+                onEnterAnimationTriggered();
             }
         }
     };
@@ -128,18 +140,37 @@
     BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
+            // Mark recents as no longer visible
+            AlternateRecentsComponent.notifyVisibilityChanged(false);
+            // Finish without an animations
             finish();
         }
     };
 
+    // A runnable to finish the Recents activity
+    Runnable mFinishRunnable = new Runnable() {
+        @Override
+        public void run() {
+            // Mark recents as no longer visible
+            AlternateRecentsComponent.notifyVisibilityChanged(false);
+            // Finish with an animations
+            finish();
+            overridePendingTransition(R.anim.recents_to_launcher_enter,
+                    R.anim.recents_to_launcher_exit);
+        }
+    };
+
     /** Updates the set of recent tasks */
     void updateRecentsTasks(Intent launchIntent) {
         // Update the configuration based on the launch intent
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        config.launchedWithThumbnailAnimation = launchIntent.getBooleanExtra(
-                AlternateRecentsComponent.EXTRA_ANIMATING_WITH_THUMBNAIL, false);
-        config.launchedFromAltTab = launchIntent.getBooleanExtra(
-                AlternateRecentsComponent.EXTRA_FROM_ALT_TAB, false);
+        mConfig.launchedFromHome = launchIntent.getBooleanExtra(
+                AlternateRecentsComponent.EXTRA_FROM_HOME, false);
+        mConfig.launchedFromAppWithThumbnail = launchIntent.getBooleanExtra(
+                AlternateRecentsComponent.EXTRA_FROM_APP_THUMBNAIL, false);
+        mConfig.launchedFromAppWithScreenshot = launchIntent.getBooleanExtra(
+                AlternateRecentsComponent.EXTRA_FROM_APP_FULL_SCREENSHOT, false);
+        mConfig.launchedWithAltTab = launchIntent.getBooleanExtra(
+                AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
 
         RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
         SpaceNode root = loader.reload(this, Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount);
@@ -165,7 +196,6 @@
     /** Attempts to allocate and bind the search bar app widget */
     void bindSearchBarAppWidget() {
         if (Constants.DebugFlags.App.EnableSearchLayout) {
-            RecentsConfiguration config = RecentsConfiguration.getInstance();
             SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
 
             // Reset the host view and widget info
@@ -173,7 +203,7 @@
             mSearchAppWidgetInfo = null;
 
             // Try and load the app widget id from the settings
-            int appWidgetId = config.searchBarAppWidgetId;
+            int appWidgetId = mConfig.searchBarAppWidgetId;
             if (appWidgetId >= 0) {
                 mSearchAppWidgetInfo = ssp.getAppWidgetInfo(appWidgetId);
                 if (mSearchAppWidgetInfo == null) {
@@ -203,7 +233,7 @@
                     }
 
                     // Save the app widget id into the settings
-                    config.updateSearchBarAppWidgetId(this, widgetInfo.first);
+                    mConfig.updateSearchBarAppWidgetId(this, widgetInfo.first);
                     mSearchAppWidgetInfo = widgetInfo.second;
                 }
             }
@@ -213,8 +243,7 @@
     /** Creates the search bar app widget view */
     void addSearchBarAppWidgetView() {
         if (Constants.DebugFlags.App.EnableSearchLayout) {
-            RecentsConfiguration config = RecentsConfiguration.getInstance();
-            int appWidgetId = config.searchBarAppWidgetId;
+            int appWidgetId = mConfig.searchBarAppWidgetId;
             if (appWidgetId >= 0) {
                 if (Console.Enabled) {
                     Console.log(Constants.Log.App.SystemUIHandshake,
@@ -240,9 +269,19 @@
     /** Dismisses recents if we are already visible and the intent is to toggle the recents view */
     boolean dismissRecentsIfVisible() {
         if (mVisible) {
-            if (!mRecentsView.launchFocusedTask()) {
-                if (!mRecentsView.launchFirstTask()) {
-                    finish();
+            // If we are mid-animation into Recents, then reverse it and finish
+            if (mFullScreenshotView == null ||
+                    !mFullScreenshotView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
+                // If we have a focused task, then launch that task
+                if (!mRecentsView.launchFocusedTask()) {
+                    // If there are any tasks, then launch the first task
+                    if (!mRecentsView.launchFirstTask()) {
+                        // We really shouldn't hit this, but if we do, just animate out (aka. finish)
+                        ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
+                                null, mFinishRunnable, null);
+                        mRecentsView.startOnExitAnimation(
+                                new ViewAnimation.TaskViewExitContext(exitTrigger));
+                    }
                 }
             }
             return true;
@@ -264,7 +303,7 @@
 
         // Initialize the loader and the configuration
         RecentsTaskLoader.initialize(this);
-        RecentsConfiguration.reinitialize(this);
+        mConfig = RecentsConfiguration.reinitialize(this);
 
         // Initialize the widget host (the host id is static and does not change)
         mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId, this);
@@ -286,16 +325,29 @@
         mNavBarScrimView.setLayoutParams(new FrameLayout.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM));
+        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
+            mFullScreenshotView = new FullScreenTransitionView(this, this);
+            mFullScreenshotView.setLayoutParams(new FrameLayout.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+        }
 
         mContainerView = new FrameLayout(this);
         mContainerView.addView(mRecentsView);
         mContainerView.addView(mEmptyView);
+        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
+            mContainerView.addView(mFullScreenshotView);
+        }
         mContainerView.addView(mNavBarScrimView);
         setContentView(mContainerView);
 
         // Update the recent tasks
         updateRecentsTasks(getIntent());
 
+        // Prepare the screenshot transition if necessary
+        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
+            mFullScreenshotView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
+        }
+
         // Bind the search app widget when we first start up
         bindSearchBarAppWidget();
         // Add the search bar layout
@@ -319,7 +371,9 @@
 
     void onConfigurationChange() {
         // Try and start the enter animation (or restart it on configuration changed)
-        mRecentsView.startOnEnterAnimation();
+        mRecentsView.startOnEnterAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenshotView));
+        // Call our callback
+        onEnterAnimationTriggered();
     }
 
     @Override
@@ -338,11 +392,16 @@
 
         // Initialize the loader and the configuration
         RecentsTaskLoader.initialize(this);
-        RecentsConfiguration.reinitialize(this);
+        mConfig = RecentsConfiguration.reinitialize(this);
 
         // Update the recent tasks
         updateRecentsTasks(intent);
 
+        // Prepare the screenshot transition if necessary
+        if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
+            mFullScreenshotView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
+        }
+
         // Don't attempt to rebind the search bar widget, but just add the search bar layout
         addSearchBarAppWidgetView();
     }
@@ -356,8 +415,7 @@
         super.onStart();
 
         // Start listening for widget package changes if there is one bound
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        if (config.searchBarAppWidgetId >= 0) {
+        if (mConfig.searchBarAppWidgetId >= 0) {
             mAppWidgetHost.startListening();
         }
 
@@ -431,8 +489,7 @@
         super.onStop();
 
         // Stop listening for widget package changes if there was one bound
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        if (config.searchBarAppWidgetId >= 0) {
+        if (mConfig.searchBarAppWidgetId >= 0) {
             mAppWidgetHost.stopListening();
         }
 
@@ -471,54 +528,85 @@
 
     @Override
     public void onBackPressed() {
-        // Unfilter any stacks
-        if (!mRecentsView.unfilterFilteredStacks()) {
-            if (!mRecentsView.launchFirstTask()) {
-                super.onBackPressed();
+        // If we are mid-animation into Recents, then reverse it and finish
+        if (mFullScreenshotView == null ||
+                !mFullScreenshotView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
+            // If we are currently filtering in any stacks, unfilter them first
+            if (!mRecentsView.unfilterFilteredStacks()) {
+                if (mConfig.launchedFromHome) {
+                    // Just start the animation out of recents
+                    ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
+                            null, mFinishRunnable, null);
+                    mRecentsView.startOnExitAnimation(
+                            new ViewAnimation.TaskViewExitContext(exitTrigger));
+                } else {
+                    // Otherwise, try and launch the first task
+                    if (!mRecentsView.launchFirstTask()) {
+                        // If there are no tasks, then just finish recents
+                        ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
+                                null, mFinishRunnable, null);
+                        mRecentsView.startOnExitAnimation(
+                                new ViewAnimation.TaskViewExitContext(exitTrigger));
+                    }
+                }
             }
         }
     }
 
-    @Override
     public void onEnterAnimationTriggered() {
         // Fade in the scrim
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        if (config.hasNavBarScrim()) {
+        if (mConfig.hasNavBarScrim()) {
             mNavBarScrimView.setVisibility(View.VISIBLE);
             mNavBarScrimView.setAlpha(0f);
             mNavBarScrimView.animate().alpha(1f)
-                    .setStartDelay(config.taskBarEnterAnimDelay)
-                    .setDuration(config.navBarScrimEnterDuration)
-                    .setInterpolator(config.fastOutSlowInInterpolator)
+                    .setStartDelay(mConfig.taskBarEnterAnimDelay)
+                    .setDuration(mConfig.navBarScrimEnterDuration)
+                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
                     .withLayer()
                     .start();
         }
     }
 
     @Override
+    public void onEnterAnimationComplete(boolean canceled) {
+        if (!canceled) {
+            // Reset the full screenshot transition view
+            if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
+                mFullScreenshotView.reset();
+            }
+
+            // XXX: We should clean up the screenshot in this case as well, but it needs to happen
+            //      after to animate up
+        }
+        // Recycle the full screen screenshot
+        AlternateRecentsComponent.consumeLastScreenshot();
+    }
+
+    @Override
     public void onTaskLaunching(boolean isTaskInStackBounds) {
         mTaskLaunched = true;
 
         // Fade out the scrim
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        if (!isTaskInStackBounds && config.hasNavBarScrim()) {
+        if (!isTaskInStackBounds && mConfig.hasNavBarScrim()) {
             mNavBarScrimView.animate().alpha(0f)
                     .setStartDelay(0)
-                    .setDuration(config.taskBarExitAnimDuration)
-                    .setInterpolator(config.fastOutSlowInInterpolator)
+                    .setDuration(mConfig.taskBarExitAnimDuration)
+                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
                     .withLayer()
                     .start();
         }
+
+        // Mark recents as no longer visible
+        AlternateRecentsComponent.notifyVisibilityChanged(false);
     }
 
     @Override
     public void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
         SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
-        if (appWidgetId > -1 && appWidgetId == config.searchBarAppWidgetId) {
+        if (appWidgetId > -1 && appWidgetId == mConfig.searchBarAppWidgetId) {
             // The search provider may have changed, so just delete the old widget and bind it again
             ssp.unbindSearchAppWidget(mAppWidgetHost, appWidgetId);
-            config.updateSearchBarAppWidgetId(this, -1);
+            mConfig.updateSearchBarAppWidgetId(this, -1);
             // Load the widget again
             bindSearchBarAppWidget();
             addSearchBarAppWidgetView();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 0cf6ee6..a0c5253 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -37,27 +37,39 @@
 
     DisplayMetrics mDisplayMetrics;
 
-    public Rect systemInsets = new Rect();
-    public Rect displayRect = new Rect();
-
-    boolean isLandscape;
-    boolean transposeRecentsLayoutWithOrientation;
-    int searchBarAppWidgetId = -1;
-
+    /** Animations */
     public float animationPxMovementPerSecond;
 
+    /** Interpolators */
     public Interpolator fastOutSlowInInterpolator;
     public Interpolator fastOutLinearInInterpolator;
     public Interpolator linearOutSlowInInterpolator;
+    public Interpolator quintOutInterpolator;
 
+    /** Filtering */
     public int filteringCurrentViewsMinAnimDuration;
     public int filteringNewViewsMinAnimDuration;
 
-    public int taskStackScrollDismissInfoPaneDistance;
-    public int taskStackMaxDim;
-    public float taskStackWidthPaddingPct;
-    public int taskStackTopPaddingPx;
+    /** Insets */
+    public Rect systemInsets = new Rect();
+    public Rect displayRect = new Rect();
 
+    /** Layout */
+    boolean isLandscape;
+    boolean transposeRecentsLayoutWithOrientation;
+
+    /** Search bar */
+    int searchBarAppWidgetId = -1;
+    public int searchBarSpaceHeightPx;
+
+    /** Task stack */
+    public int taskStackMaxDim;
+    public int taskStackTopPaddingPx;
+    public float taskStackWidthPaddingPct;
+
+    /** Task view animation and styles */
+    public int taskViewEnterFromHomeDuration;
+    public int taskViewEnterFromHomeDelay;
     public int taskViewRemoveAnimDuration;
     public int taskViewRemoveAnimTranslationXPx;
     public int taskViewTranslationZMinPx;
@@ -66,23 +78,28 @@
     public int taskViewRoundedCornerRadiusPx;
     public int taskViewHighlightPx;
 
-    public int searchBarSpaceHeightPx;
-
+    /** Task bar colors */
     public int taskBarViewDefaultBackgroundColor;
     public int taskBarViewDefaultTextColor;
     public int taskBarViewLightTextColor;
     public int taskBarViewDarkTextColor;
     public int taskBarViewHighlightColor;
 
+    /** Task bar animations */
     public int taskBarEnterAnimDuration;
     public int taskBarEnterAnimDelay;
     public int taskBarExitAnimDuration;
 
+    /** Nav bar scrim */
     public int navBarScrimEnterDuration;
 
-    public boolean launchedFromAltTab;
-    public boolean launchedWithThumbnailAnimation;
+    /** Launch states */
+    public boolean launchedWithAltTab;
+    public boolean launchedFromAppWithThumbnail;
+    public boolean launchedFromAppWithScreenshot;
+    public boolean launchedFromHome;
 
+    /** Dev options */
     public boolean developerOptionsEnabled;
 
     /** Private constructor */
@@ -108,33 +125,54 @@
         DisplayMetrics dm = res.getDisplayMetrics();
         mDisplayMetrics = dm;
 
-        isLandscape = res.getConfiguration().orientation ==
-                Configuration.ORIENTATION_LANDSCAPE;
-        transposeRecentsLayoutWithOrientation =
-                res.getBoolean(R.bool.recents_transpose_layout_with_orientation);
-        if (Console.Enabled) {
-            Console.log(Constants.Log.UI.MeasureAndLayout,
-                    "[RecentsConfiguration|orientation]", isLandscape ? "Landscape" : "Portrait",
-                    Console.AnsiGreen);
-        }
-
-        displayRect.set(0, 0, dm.widthPixels, dm.heightPixels);
+        // Animations
         animationPxMovementPerSecond =
                 res.getDimensionPixelSize(R.dimen.recents_animation_movement_in_dps_per_second);
+
+        // Interpolators
+        fastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.fast_out_slow_in);
+        fastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.fast_out_linear_in);
+        linearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.linear_out_slow_in);
+        quintOutInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.decelerate_quint);
+
+        // Filtering
         filteringCurrentViewsMinAnimDuration =
                 res.getInteger(R.integer.recents_filter_animate_current_views_min_duration);
         filteringNewViewsMinAnimDuration =
                 res.getInteger(R.integer.recents_filter_animate_new_views_min_duration);
 
-        taskStackScrollDismissInfoPaneDistance = res.getDimensionPixelSize(
-                R.dimen.recents_task_stack_scroll_dismiss_info_pane_distance);
-        taskStackMaxDim = res.getInteger(R.integer.recents_max_task_stack_view_dim);
+        // Insets
+        displayRect.set(0, 0, dm.widthPixels, dm.heightPixels);
 
+        // Layout
+        isLandscape = res.getConfiguration().orientation ==
+                Configuration.ORIENTATION_LANDSCAPE;
+        transposeRecentsLayoutWithOrientation =
+                res.getBoolean(R.bool.recents_transpose_layout_with_orientation);
+
+        // Search bar
+        searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height);
+
+        // Update the search widget id
+        SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
+        searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1);
+
+        // Task stack
         TypedValue widthPaddingPctValue = new TypedValue();
         res.getValue(R.dimen.recents_stack_width_padding_percentage, widthPaddingPctValue, true);
         taskStackWidthPaddingPct = widthPaddingPctValue.getFloat();
+        taskStackMaxDim = res.getInteger(R.integer.recents_max_task_stack_view_dim);
         taskStackTopPaddingPx = res.getDimensionPixelSize(R.dimen.recents_stack_top_padding);
 
+        // Task view animation and styles
+        taskViewEnterFromHomeDuration =
+                res.getInteger(R.integer.recents_animate_task_enter_from_home_duration);
+        taskViewEnterFromHomeDelay =
+                res.getInteger(R.integer.recents_animate_task_enter_from_home_delay);
         taskViewRemoveAnimDuration =
                 res.getInteger(R.integer.recents_animate_task_view_remove_duration);
         taskViewRemoveAnimTranslationXPx =
@@ -148,8 +186,7 @@
         taskViewShadowOutlineBottomInsetPx =
                 res.getDimensionPixelSize(R.dimen.recents_task_view_shadow_outline_bottom_inset);
 
-        searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height);
-
+        // Task bar colors
         taskBarViewDefaultBackgroundColor =
                 res.getColor(R.color.recents_task_bar_default_background_color);
         taskBarViewDefaultTextColor =
@@ -161,6 +198,7 @@
         taskBarViewHighlightColor =
                 res.getColor(R.color.recents_task_bar_highlight_color);
 
+        // Task bar animations
         taskBarEnterAnimDuration =
                 res.getInteger(R.integer.recents_animate_task_bar_enter_duration);
         taskBarEnterAnimDelay =
@@ -168,24 +206,20 @@
         taskBarExitAnimDuration =
                 res.getInteger(R.integer.recents_animate_task_bar_exit_duration);
 
+        // Nav bar scrim
         navBarScrimEnterDuration =
                 res.getInteger(R.integer.recents_nav_bar_scrim_enter_duration);
 
-        fastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
-                        com.android.internal.R.interpolator.fast_out_slow_in);
-        fastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
-                com.android.internal.R.interpolator.fast_out_linear_in);
-        linearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
-                com.android.internal.R.interpolator.linear_out_slow_in);
-
         // Check if the developer options are enabled
         ContentResolver cr = context.getContentResolver();
         developerOptionsEnabled = Settings.Global.getInt(cr,
                 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
 
-        // Update the search widget id
-        SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
-        searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1);
+        if (Console.Enabled) {
+            Console.log(Constants.Log.UI.MeasureAndLayout,
+                    "[RecentsConfiguration|orientation]", isLandscape ? "Landscape" : "Portrait",
+                    Console.AnsiGreen);
+        }
     }
 
     /** Updates the system insets */
@@ -204,8 +238,10 @@
     /** Called when the configuration has changed, and we want to reset any configuration specific
      * members. */
     public void updateOnConfigurationChange() {
-        launchedFromAltTab = false;
-        launchedWithThumbnailAnimation = false;
+        launchedWithAltTab = false;
+        launchedFromAppWithThumbnail = false;
+        launchedFromAppWithScreenshot = false;
+        launchedFromHome = false;
     }
 
     /** Returns whether the search bar app widget exists. */
@@ -257,15 +293,4 @@
             searchBarSpaceBounds.set(0, 0, width, searchBarSpaceHeightPx);
         }
     }
-
-    /** Converts from DPs to PXs */
-    public int pxFromDp(float size) {
-        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                size, mDisplayMetrics));
-    }
-    /** Converts from SPs to PXs */
-    public int pxFromSp(float size) {
-        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
-                size, mDisplayMetrics));
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
index 113efe3..e554af7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
@@ -134,10 +134,12 @@
             context.sendBroadcast(intent);
 
             // Time this path
-            Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
-                    Constants.Log.App.TimeRecentsStartupKey, "receivedToggleRecents");
-            Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
-                    Constants.Log.App.TimeRecentsLaunchKey, "receivedToggleRecents");
+            if (Console.Enabled) {
+                Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
+                        Constants.Log.App.TimeRecentsStartupKey, "receivedToggleRecents");
+                Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
+                        Constants.Log.App.TimeRecentsLaunchKey, "receivedToggleRecents");
+            }
         } else if (msg.what == AlternateRecentsComponent.MSG_START_ENTER_ANIMATION) {
             // Send a broadcast to start the enter animation
             Intent intent = new Intent(RecentsService.ACTION_START_ENTER_ANIMATION);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
index 4685186..dbcdb94 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
@@ -478,7 +478,7 @@
                 // Load the thumbnail (if possible and not the foremost task, from the cache)
                 if (!isForemostTask) {
                     task.thumbnail = mThumbnailCache.get(task.key);
-                    if (task.thumbnail != null) {
+                    if (task.thumbnail != null && !tasksToForceLoad.contains(task)) {
                         // Even though we get things from the cache, we should update them if
                         // they've changed in the bg
                         tasksToForceLoad.add(task);
@@ -489,6 +489,7 @@
                         Console.log(Constants.Log.App.TaskDataLoader,
                                 "[RecentsTaskLoader|loadingTaskThumbnail]");
                     }
+
                     task.thumbnail = ssp.getTaskThumbnail(task.key.id);
                     if (task.thumbnail != null) {
                         task.thumbnail.setHasAlpha(false);
@@ -512,20 +513,6 @@
                     "" + (System.currentTimeMillis() - t1) + "ms");
         }
 
-        /*
-        // Get all the stacks
-        t1 = System.currentTimeMillis();
-        List<ActivityManager.StackInfo> stackInfos = ams.getAllStackInfos();
-        Console.log(Constants.Log.App.TimeSystemCalls, "[RecentsTaskLoader|getAllStackInfos]", "" + (System.currentTimeMillis() - t1) + "ms");
-        Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|stacks]", "" + tasks.size());
-        for (ActivityManager.StackInfo s : stackInfos) {
-            Console.log(Constants.Log.App.TaskDataLoader, "  [RecentsTaskLoader|stack]", s.toString());
-            if (stacks.containsKey(s.stackId)) {
-                stacks.get(s.stackId).setRect(s.bounds);
-            }
-        }
-        */
-
         // Start the task loader
         mLoader.start(context);
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ReferenceCountedTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/ReferenceCountedTrigger.java
new file mode 100644
index 0000000..2f89e6d2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/ReferenceCountedTrigger.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+import android.content.Context;
+
+/**
+ * A ref counted trigger that does some logic when the count is first incremented, or last
+ * decremented.  Not thread safe as it's not currently needed.
+ */
+public class ReferenceCountedTrigger {
+
+    Context mContext;
+    int mCount;
+    Runnable mFirstIncRunnable;
+    Runnable mLastDecRunnable;
+    Runnable mErrorRunnable;
+
+    // Convenience runnables
+    Runnable mIncrementRunnable = new Runnable() {
+        @Override
+        public void run() {
+            increment();
+        }
+    };
+    Runnable mDecrementRunnable = new Runnable() {
+        @Override
+        public void run() {
+            decrement();
+        }
+    };
+
+    public ReferenceCountedTrigger(Context context, Runnable firstIncRunnable,
+                                   Runnable lastDecRunnable, Runnable errorRunanable) {
+        mContext = context;
+        mFirstIncRunnable = firstIncRunnable;
+        mLastDecRunnable = lastDecRunnable;
+        mErrorRunnable = errorRunanable;
+    }
+
+    /** Increments the ref count */
+    public void increment() {
+        if (mCount == 0 && mFirstIncRunnable != null) {
+            mFirstIncRunnable.run();
+        }
+        mCount++;
+    }
+
+    /** Convenience method to increment this trigger as a runnable */
+    public Runnable incrementAsRunnable() {
+        return mIncrementRunnable;
+    }
+
+    /** Decrements the ref count */
+    public void decrement() {
+        mCount--;
+        if (mCount == 0 && mLastDecRunnable != null) {
+            mLastDecRunnable.run();
+        } else if (mCount < 0) {
+            if (mErrorRunnable != null) {
+                mErrorRunnable.run();
+            } else {
+                new Throwable("Invalid ref count").printStackTrace();
+                Console.logError(mContext, "Invalid ref count");
+            }
+        }
+    }
+
+    /** Convenience method to decrement this trigger as a runnable */
+    public Runnable decrementAsRunnable() {
+        return mDecrementRunnable;
+    }
+
+    /** Returns the current ref count */
+    public int getCount() {
+        return mCount;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
index 7a3ffb8..f532aa0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
@@ -30,13 +30,20 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
+import android.graphics.Matrix;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.DisplayMetrics;
 import android.util.Pair;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
 
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -54,6 +61,8 @@
     IPackageManager mIpm;
     UserManager mUm;
     SearchManager mSm;
+    WindowManager mWm;
+    Display mDisplay;
     String mRecentsPackage;
     ComponentName mAssistComponent;
 
@@ -67,6 +76,8 @@
         mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
         mIpm = AppGlobals.getPackageManager();
         mSm = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
+        mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        mDisplay = mWm.getDefaultDisplay();
         mRecentsPackage = context.getPackageName();
 
         // Resolve the assist intent
@@ -325,4 +336,13 @@
         // Delete the app widget
         host.deleteAppWidgetId(appWidgetId);
     }
+
+    /**
+     * Takes a screenshot of the current surface.
+     */
+    public Bitmap takeScreenshot() {
+        DisplayInfo di = new DisplayInfo();
+        mDisplay.getDisplayInfo(di);
+        return SurfaceControl.screenshot(di.getNaturalWidth(), di.getNaturalHeight());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FullScreenTransitionView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FullScreenTransitionView.java
new file mode 100644
index 0000000..ad2fa8d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FullScreenTransitionView.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import com.android.systemui.recents.Console;
+import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
+
+
+/**
+ * The full screen transition view that gets animated down from the full screen into a task
+ * thumbnail view.
+ */
+public class FullScreenTransitionView extends FrameLayout {
+
+    /** The FullScreenTransitionView callbacks */
+    public interface FullScreenTransitionViewCallbacks {
+        void onEnterAnimationComplete(boolean canceled);
+    }
+
+    RecentsConfiguration mConfig;
+
+    FullScreenTransitionViewCallbacks mCb;
+
+    ImageView mScreenshotView;
+
+    Rect mClipRect = new Rect();
+
+    boolean mIsAnimating;
+    AnimatorSet mEnterAnimation;
+
+    public FullScreenTransitionView(Context context, FullScreenTransitionViewCallbacks cb) {
+        super(context);
+        mConfig = RecentsConfiguration.getInstance();
+        mCb = cb;
+        mScreenshotView = new ImageView(context);
+        mScreenshotView.setScaleType(ImageView.ScaleType.FIT_XY);
+        mScreenshotView.setLayoutParams(new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+        addView(mScreenshotView);
+        setClipTop(getClipTop());
+        setClipBottom(getClipBottom());
+        setWillNotDraw(false);
+    }
+
+    /** Sets the top clip */
+    public void setClipTop(int clip) {
+        mClipRect.top = clip;
+        postInvalidateOnAnimation();
+    }
+
+    /** Gets the top clip */
+    public int getClipTop() {
+        return mClipRect.top;
+    }
+
+    /** Sets the bottom clip */
+    public void setClipBottom(int clip) {
+        mClipRect.bottom = clip;
+        postInvalidateOnAnimation();
+    }
+
+    /** Gets the top clip */
+    public int getClipBottom() {
+        return mClipRect.bottom;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        mClipRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        int restoreCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+        canvas.clipRect(mClipRect);
+        super.draw(canvas);
+        canvas.restoreToCount(restoreCount);
+    }
+
+    /** Prepares the screenshot view for the transition into Recents */
+    public void prepareAnimateOnEnterRecents(Bitmap screenshot) {
+        if (!mConfig.launchedFromAppWithScreenshot) return;
+
+        if (Console.Enabled) {
+            Console.logStartTracingTime(Constants.Log.App.TimeRecentsScreenshotTransition,
+                    Constants.Log.App.TimeRecentsScreenshotTransitionKey);
+        }
+
+        setClipTop(0);
+        setClipBottom(getMeasuredHeight());
+        setTranslationY(0f);
+        setScaleX(1f);
+        setScaleY(1f);
+        setVisibility(mConfig.launchedFromAppWithScreenshot ? View.VISIBLE : View.INVISIBLE);
+        if (screenshot != null) {
+            mScreenshotView.setImageBitmap(screenshot);
+        } else {
+            mScreenshotView.setImageDrawable(null);
+        }
+    }
+
+    /** Resets the transition view */
+    public void reset() {
+        setVisibility(View.INVISIBLE);
+        mScreenshotView.setImageDrawable(null);
+    }
+
+    /** Animates this view as it enters recents */
+    public void animateOnEnterRecents(ViewAnimation.TaskViewEnterContext ctx,
+                                      final Runnable postAnimRunnable) {
+        if (Console.Enabled) {
+            Console.logTraceTime(Constants.Log.App.TimeRecentsScreenshotTransition,
+                    Constants.Log.App.TimeRecentsScreenshotTransitionKey, "Starting");
+        }
+
+        // Cancel the current animation
+        if (mEnterAnimation != null) {
+            mEnterAnimation.removeAllListeners();
+            mEnterAnimation.cancel();
+        }
+
+        // Calculate the bottom clip
+        float scale = (float) ctx.taskRect.width() / getMeasuredWidth();
+        int translationY = -mConfig.systemInsets.top + ctx.stackRectSansPeek.top +
+                ctx.transform.translationY;
+        int clipBottom = mConfig.systemInsets.top + (int) (ctx.taskRect.height() / scale);
+
+        // Enable the HW Layers on the screenshot view
+        mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+
+        // Compose the animation
+        mEnterAnimation = new AnimatorSet();
+        mEnterAnimation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                // Notify any callbacks
+                mCb.onEnterAnimationComplete(false);
+                // Run the given post-anim runnable
+                postAnimRunnable.run();
+                // Mark that we are no longer animating
+                mIsAnimating = false;
+                // Disable the HW Layers on this view
+                setLayerType(View.LAYER_TYPE_NONE, null);
+
+                if (Console.Enabled) {
+                    Console.logTraceTime(Constants.Log.App.TimeRecentsScreenshotTransition,
+                            Constants.Log.App.TimeRecentsScreenshotTransitionKey, "Completed");
+                }
+            }
+        });
+        mEnterAnimation.setStartDelay(0);
+        mEnterAnimation.setDuration(475);
+        mEnterAnimation.setInterpolator(mConfig.fastOutSlowInInterpolator);
+        mEnterAnimation.playTogether(
+                ObjectAnimator.ofInt(this, "clipTop", mConfig.systemInsets.top),
+                ObjectAnimator.ofInt(this, "clipBottom", clipBottom),
+                ObjectAnimator.ofFloat(this, "translationY", translationY),
+                ObjectAnimator.ofFloat(this, "scaleX", scale),
+                ObjectAnimator.ofFloat(this, "scaleY", scale)
+        );
+        mEnterAnimation.start();
+
+        mIsAnimating = true;
+    }
+
+    /** Animates this view back out of Recents if we were in the process of animating in. */
+    public boolean cancelAnimateOnEnterRecents(final Runnable postAnimRunnable) {
+        if (mIsAnimating) {
+            // Cancel the current animation
+            if (mEnterAnimation != null) {
+                mEnterAnimation.removeAllListeners();
+                mEnterAnimation.cancel();
+            }
+
+            // Compose the animation
+            mEnterAnimation = new AnimatorSet();
+            mEnterAnimation.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    // Notify any callbacks
+                    mCb.onEnterAnimationComplete(true);
+                    // Run the given post-anim runnable
+                    postAnimRunnable.run();
+                    // Mark that we are no longer animating
+                    mIsAnimating = false;
+                    // Disable the HW Layers on the screenshot view
+                    mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
+                }
+            });
+            mEnterAnimation.setDuration(475);
+            mEnterAnimation.setInterpolator(mConfig.fastOutSlowInInterpolator);
+            mEnterAnimation.playTogether(
+                    ObjectAnimator.ofInt(this, "clipTop", 0),
+                    ObjectAnimator.ofInt(this, "clipBottom", getMeasuredHeight()),
+                    ObjectAnimator.ofFloat(this, "translationY", 0f),
+                    ObjectAnimator.ofFloat(this, "scaleX", 1f),
+                    ObjectAnimator.ofFloat(this, "scaleY", 1f)
+            );
+            mEnterAnimation.start();
+
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index db398b1..a2c250c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -55,9 +55,11 @@
     /** The RecentsView callbacks */
     public interface RecentsViewCallbacks {
         public void onTaskLaunching(boolean isTaskInStackBounds);
-        public void onEnterAnimationTriggered();
     }
 
+    RecentsConfiguration mConfig;
+    LayoutInflater mInflater;
+
     // The space partitioning root of this container
     SpaceNode mBSP;
     // Whether there are any tasks
@@ -67,10 +69,9 @@
     // Recents view callbacks
     RecentsViewCallbacks mCb;
 
-    LayoutInflater mInflater;
-
     public RecentsView(Context context) {
         super(context);
+        mConfig = RecentsConfiguration.getInstance();
         mInflater = LayoutInflater.from(context);
         setWillNotDraw(false);
     }
@@ -160,20 +161,39 @@
     }
 
     /** Requests all task stacks to start their enter-recents animation */
-    public void startOnEnterAnimation() {
-        // Notify callbacks that we are starting the enter animation
-        mCb.onEnterAnimationTriggered();
-
+    public void startOnEnterAnimation(ViewAnimation.TaskViewEnterContext ctx) {
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
             if (child instanceof TaskStackView) {
                 TaskStackView stackView = (TaskStackView) child;
-                stackView.startOnEnterAnimation();
+                stackView.startOnEnterAnimation(ctx);
             }
         }
     }
 
+    /** Requests all task stacks to start their exit-recents animation */
+    public void startOnExitAnimation(ViewAnimation.TaskViewExitContext ctx) {
+        // Handle the case when there are no views by incrementing and decrementing after all
+        // animations are started.
+        ctx.postAnimationTrigger.increment();
+
+        if (Constants.DebugFlags.App.EnableHomeTransition) {
+            int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                View child = getChildAt(i);
+                if (child instanceof TaskStackView) {
+                    TaskStackView stackView = (TaskStackView) child;
+                    stackView.startOnExitAnimation(ctx);
+                }
+            }
+        }
+
+        // Handle the case when there are no views by incrementing and decrementing after all
+        // animations are started.
+        ctx.postAnimationTrigger.decrement();
+    }
+
     /** Adds the search bar */
     public void setSearchBar(View searchBar) {
         // Create the search bar (and hide it if we have no recent tasks)
@@ -215,10 +235,9 @@
         }
 
         // Get the search bar bounds and measure the search bar layout
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
         if (mSearchBar != null) {
             Rect searchBarSpaceBounds = new Rect();
-            config.getSearchBarBounds(width, height - config.systemInsets.top, searchBarSpaceBounds);
+            mConfig.getSearchBarBounds(width, height - mConfig.systemInsets.top, searchBarSpaceBounds);
             mSearchBar.measure(
                     MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY),
                     MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY));
@@ -229,9 +248,9 @@
         // In addition, we give it the full height, not including the top inset or search bar space,
         // since we want the tasks to render under the navigation buttons in portrait.
         Rect taskStackBounds = new Rect();
-        config.getTaskStackBounds(width, height, taskStackBounds);
-        int childWidth = width - config.systemInsets.right;
-        int childHeight = taskStackBounds.height() - config.systemInsets.top;
+        mConfig.getTaskStackBounds(width, height, taskStackBounds);
+        int childWidth = width - mConfig.systemInsets.right;
+        int childHeight = taskStackBounds.height() - mConfig.systemInsets.top;
 
         // Measure each TaskStackView
         int childCount = getChildCount();
@@ -259,23 +278,22 @@
         }
 
         // Get the search bar bounds so that we lay it out
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
         if (mSearchBar != null) {
             Rect searchBarSpaceBounds = new Rect();
-            config.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(), searchBarSpaceBounds);
-            mSearchBar.layout(config.systemInsets.left + searchBarSpaceBounds.left,
-                    config.systemInsets.top + searchBarSpaceBounds.top,
-                    config.systemInsets.left + mSearchBar.getMeasuredWidth(),
-                    config.systemInsets.top + mSearchBar.getMeasuredHeight());
+            mConfig.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(), searchBarSpaceBounds);
+            mSearchBar.layout(mConfig.systemInsets.left + searchBarSpaceBounds.left,
+                    mConfig.systemInsets.top + searchBarSpaceBounds.top,
+                    mConfig.systemInsets.left + mSearchBar.getMeasuredWidth(),
+                    mConfig.systemInsets.top + mSearchBar.getMeasuredHeight());
         }
 
         // We offset the stack view by the left inset (if any), but lay it out under the search bar.
         // In addition, we offset our stack views by the top inset and search bar height, but not
         // the bottom insets because we want it to render under the navigation buttons.
         Rect taskStackBounds = new Rect();
-        config.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds);
-        left += config.systemInsets.left;
-        top += config.systemInsets.top + taskStackBounds.top;
+        mConfig.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds);
+        left += mConfig.systemInsets.left;
+        top += mConfig.systemInsets.top + taskStackBounds.top;
 
         // Layout each child
         // XXX: Based on the space node for that task view
@@ -324,8 +342,7 @@
         }
 
         // Update the configuration with the latest system insets and trigger a relayout
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        config.updateSystemInsets(insets.getSystemWindowInsets());
+        mConfig.updateSystemInsets(insets.getSystemWindowInsets());
         requestLayout();
 
         return insets.consumeSystemWindowInsets(false, false, false, true);
@@ -365,6 +382,11 @@
         final Runnable launchRunnable = new Runnable() {
             @Override
             public void run() {
+                if (Console.Enabled) {
+                    Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
+                            Constants.Log.App.TimeRecentsLaunchKey, "preStartActivity");
+                }
+
                 TaskViewTransform transform;
                 View sourceView = tv;
                 int offsetX = 0;
@@ -374,11 +396,10 @@
                     // If there is no actual task view, then use the stack view as the source view
                     // and then offset to the expected transform rect, but bound this to just
                     // outside the display rect (to ensure we don't animate from too far away)
-                    RecentsConfiguration config = RecentsConfiguration.getInstance();
                     sourceView = stackView;
                     transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
                     offsetX = transform.rect.left;
-                    offsetY = Math.min(transform.rect.top, config.displayRect.height());
+                    offsetY = Math.min(transform.rect.top, mConfig.displayRect.height());
                 } else {
                     transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
                 }
@@ -426,19 +447,23 @@
                     onTaskRemoved(task);
                 }
 
-                Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
-                        Constants.Log.App.TimeRecentsLaunchKey, "startActivity");
+                if (Console.Enabled) {
+                    Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
+                            Constants.Log.App.TimeRecentsLaunchKey, "startActivity");
+                }
             }
         };
 
-        Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
-                Constants.Log.App.TimeRecentsLaunchKey, "onTaskLaunched");
+        if (Console.Enabled) {
+            Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
+                    Constants.Log.App.TimeRecentsLaunchKey, "onTaskLaunched");
+        }
 
         // Launch the app right away if there is no task view, otherwise, animate the icon out first
         if (tv == null) {
             post(launchRunnable);
         } else {
-            tv.animateOnLeavingRecents(launchRunnable);
+            tv.animateOnLaunchingTask(launchRunnable);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
index c10ddd1..2c637a8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
@@ -22,8 +22,10 @@
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.view.View;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -36,6 +38,9 @@
 
 /* The task bar view */
 class TaskBarView extends FrameLayout {
+
+    RecentsConfiguration mConfig;
+
     Task mTask;
 
     ImageView mDismissButton;
@@ -61,6 +66,7 @@
 
     public TaskBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        mConfig = RecentsConfiguration.getInstance();
         setWillNotDraw(false);
 
         // Load the dismiss resources
@@ -70,11 +76,10 @@
 
         // Configure the highlight paint
         if (sHighlightPaint == null) {
-            RecentsConfiguration config = RecentsConfiguration.getInstance();
             sHighlightPaint = new Paint();
             sHighlightPaint.setStyle(Paint.Style.STROKE);
-            sHighlightPaint.setStrokeWidth(config.taskViewHighlightPx);
-            sHighlightPaint.setColor(config.taskBarViewHighlightColor);
+            sHighlightPaint.setStrokeWidth(mConfig.taskViewHighlightPx);
+            sHighlightPaint.setColor(mConfig.taskBarViewHighlightColor);
             sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
             sHighlightPaint.setAntiAlias(true);
         }
@@ -90,11 +95,9 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-
         // Draw the highlight at the top edge (but put the bottom edge just out of view)
-        float offset = config.taskViewHighlightPx / 2f;
-        float radius = config.taskViewRoundedCornerRadiusPx;
+        float offset = mConfig.taskViewHighlightPx / 2f;
+        float radius = mConfig.taskViewRoundedCornerRadiusPx;
         canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
                 getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
     }
@@ -102,7 +105,6 @@
     /** Synchronizes this bar view's properties with the task's transform */
     void updateViewPropertiesToTaskTransform(TaskViewTransform animateFromTransform,
                                              TaskViewTransform toTransform, int duration) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
         if (duration > 0) {
             if (animateFromTransform != null) {
                 mDismissButton.setAlpha(animateFromTransform.dismissAlpha);
@@ -111,18 +113,16 @@
                     .alpha(toTransform.dismissAlpha)
                     .setStartDelay(0)
                     .setDuration(duration)
-                    .setInterpolator(config.fastOutSlowInInterpolator)
+                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
                     .withLayer()
                     .start();
         } else {
             mDismissButton.setAlpha(toTransform.dismissAlpha);
         }
-        mDismissButton.invalidate();
     }
 
     /** Binds the bar view to the task */
     void rebindToTask(Task t, boolean animate) {
-        RecentsConfiguration configuration = RecentsConfiguration.getInstance();
         mTask = t;
         // If an activity icon is defined, then we use that as the primary icon to show in the bar,
         // otherwise, we fall back to the application icon
@@ -137,12 +137,12 @@
         if (Constants.DebugFlags.App.EnableTaskBarThemeColors && tint != 0) {
             setBackgroundColor(tint);
             mActivityDescription.setTextColor(Utilities.getIdealColorForBackgroundColor(tint,
-                    configuration.taskBarViewLightTextColor, configuration.taskBarViewDarkTextColor));
+                    mConfig.taskBarViewLightTextColor, mConfig.taskBarViewDarkTextColor));
             mDismissButton.setImageDrawable(Utilities.getIdealResourceForBackgroundColor(tint,
                     mLightDismissDrawable, mDarkDismissDrawable));
         } else {
-            setBackgroundColor(configuration.taskBarViewDefaultBackgroundColor);
-            mActivityDescription.setTextColor(configuration.taskBarViewDefaultTextColor);
+            setBackgroundColor(mConfig.taskBarViewDefaultBackgroundColor);
+            mActivityDescription.setTextColor(mConfig.taskBarViewDefaultTextColor);
         }
         if (animate) {
             // XXX: Investigate how expensive it will be to create a second bitmap and crossfade
@@ -155,4 +155,51 @@
         mApplicationIcon.setImageDrawable(null);
         mActivityDescription.setText("");
     }
+
+    /** Prepares this task view for the enter-recents animations.  This is called earlier in the
+     * first layout because the actual animation into recents may take a long time. */
+    public void prepareAnimateEnterRecents() {
+        setVisibility(View.INVISIBLE);
+    }
+
+    /** Animates this task bar as it enters recents */
+    public void animateOnEnterRecents(int delay) {
+        // Animate the task bar of the first task view
+        setVisibility(View.VISIBLE);
+        setTranslationY(-getMeasuredHeight());
+        animate()
+                .translationY(0)
+                .setStartDelay(delay > -1 ? delay : mConfig.taskBarEnterAnimDelay)
+                .setInterpolator(mConfig.fastOutSlowInInterpolator)
+                .setDuration(mConfig.taskBarEnterAnimDuration)
+                .withLayer()
+                .start();
+    }
+
+    /** Animates this task bar as it exits recents */
+    public void animateOnLaunchingTask(final Runnable r) {
+        animate()
+                .translationY(-getMeasuredHeight())
+                .setStartDelay(0)
+                .setInterpolator(mConfig.fastOutLinearInInterpolator)
+                .setDuration(mConfig.taskBarExitAnimDuration)
+                .withLayer()
+                .withEndAction(new Runnable() {
+                    @Override
+                    public void run() {
+                        post(r);
+                    }
+                })
+                .start();
+    }
+
+    /** Enable the hw layers on this task view */
+    void enableHwLayers() {
+        mDismissButton.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+    }
+
+    /** Disable the hw layers on this task view */
+    void disableHwLayers() {
+        mDismissButton.setLayerType(View.LAYER_TYPE_NONE, null);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 8d9f8be..186565b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -41,6 +41,7 @@
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.RecentsPackageMonitor;
 import com.android.systemui.recents.RecentsTaskLoader;
+import com.android.systemui.recents.ReferenceCountedTrigger;
 import com.android.systemui.recents.Utilities;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
@@ -62,10 +63,13 @@
         public void onTaskRemoved(Task t);
     }
 
+    RecentsConfiguration mConfig;
+
     TaskStack mStack;
     TaskStackViewTouchHandler mTouchHandler;
     TaskStackViewCallbacks mCb;
     ViewPool<TaskView, Task> mViewPool;
+    ArrayList<TaskViewTransform> mTaskTransforms = new ArrayList<TaskViewTransform>();
 
     // The various rects that define the stack view
     Rect mRect = new Rect();
@@ -83,11 +87,12 @@
     ObjectAnimator mScrollAnimator;
 
     // Optimizations
-    int mHwLayersRefCount;
+    ReferenceCountedTrigger mHwLayersTrigger;
     int mStackViewsAnimationDuration;
     boolean mStackViewsDirty = true;
     boolean mAwaitingFirstLayout = true;
     boolean mStartEnterAnimationRequestedAfterLayout;
+    ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
     int[] mTmpVisibleRange = new int[2];
     Rect mTmpRect = new Rect();
     Rect mTmpRect2 = new Rect();
@@ -95,12 +100,40 @@
 
     public TaskStackView(Context context, TaskStack stack) {
         super(context);
+        mConfig = RecentsConfiguration.getInstance();
         mStack = stack;
         mStack.setCallbacks(this);
         mScroller = new OverScroller(context);
         mTouchHandler = new TaskStackViewTouchHandler(context, this);
         mViewPool = new ViewPool<TaskView, Task>(context, this);
         mInflater = LayoutInflater.from(context);
+        mHwLayersTrigger = new ReferenceCountedTrigger(getContext(), new Runnable() {
+            @Override
+            public void run() {
+                // Enable hw layers on each of the children
+                int childCount = getChildCount();
+                for (int i = 0; i < childCount; i++) {
+                    TaskView tv = (TaskView) getChildAt(i);
+                    tv.enableHwLayers();
+                }
+            }
+        }, new Runnable() {
+            @Override
+            public void run() {
+                // Disable hw layers on each of the children
+                int childCount = getChildCount();
+                for (int i = 0; i < childCount; i++) {
+                    TaskView tv = (TaskView) getChildAt(i);
+                    tv.disableHwLayers();
+                }
+            }
+        }, new Runnable() {
+            @Override
+            public void run() {
+                new Throwable("Invalid hw layers ref count").printStackTrace();
+                Console.logError(getContext(), "Invalid HW layers ref count");
+            }
+        });
     }
 
     /** Sets the callbacks */
@@ -118,7 +151,7 @@
                     "[TaskStackView|requestSynchronize]", "" + duration + "ms", Console.AnsiYellow);
         }
         if (!mStackViewsDirty) {
-            invalidate();
+            invalidate(mStackRect);
         }
         if (mAwaitingFirstLayout) {
             // Skip the animation if we are awaiting first layout
@@ -165,7 +198,7 @@
         float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2;
         transform.scale = scale;
 
-        // Set the translation
+        // Set the y translation
         if (boundedT < 0f) {
             transform.translationY = (int) ((Math.max(-numPeekCards, boundedT) /
                     numPeekCards) * peekHeight - scaleYOffset);
@@ -174,9 +207,8 @@
         }
 
         // Set the z translation
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        int minZ = config.taskViewTranslationZMinPx;
-        int incZ = config.taskViewTranslationZIncrementPx;
+        int minZ = mConfig.taskViewTranslationZMinPx;
+        int incZ = mConfig.taskViewTranslationZIncrementPx;
         transform.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ));
 
         // Set the alphas
@@ -198,16 +230,18 @@
     /**
      * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
      */
-    private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks,
-                                                            int stackScroll,
-                                                            int[] visibleRangeOut,
-                                                            boolean boundTranslationsToRect) {
+    private void updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
+                                       ArrayList<Task> tasks,
+                                       int stackScroll,
+                                       int[] visibleRangeOut,
+                                       boolean boundTranslationsToRect) {
         // XXX: Optimization: Use binary search to find the visible range
 
-        ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>();
         int taskCount = tasks.size();
         int firstVisibleIndex = -1;
         int lastVisibleIndex = -1;
+        taskTransforms.clear();
+        taskTransforms.ensureCapacity(taskCount);
         for (int i = 0; i < taskCount; i++) {
             TaskViewTransform transform = getStackTransform(i, stackScroll);
             taskTransforms.add(transform);
@@ -226,6 +260,19 @@
             visibleRangeOut[0] = firstVisibleIndex;
             visibleRangeOut[1] = lastVisibleIndex;
         }
+    }
+
+    /**
+     * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. This
+     * call is less optimal than calling updateStackTransforms directly.
+     */
+    private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks,
+                                                            int stackScroll,
+                                                            int[] visibleRangeOut,
+                                                            boolean boundTranslationsToRect) {
+        ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>();
+        updateStackTransforms(taskTransforms, tasks, stackScroll, visibleRangeOut,
+                boundTranslationsToRect);
         return taskTransforms;
     }
 
@@ -245,14 +292,13 @@
             int[] visibleRange = mTmpVisibleRange;
             int stackScroll = getStackScroll();
             ArrayList<Task> tasks = mStack.getTasks();
-            ArrayList<TaskViewTransform> taskTransforms = getStackTransforms(tasks, stackScroll,
-                    visibleRange, false);
+            updateStackTransforms(mTaskTransforms, tasks, stackScroll, visibleRange, false);
 
             // Update the visible state of all the tasks
             int taskCount = tasks.size();
             for (int i = 0; i < taskCount; i++) {
                 Task task = tasks.get(i);
-                TaskViewTransform transform = taskTransforms.get(i);
+                TaskViewTransform transform = mTaskTransforms.get(i);
                 TaskView tv = getChildViewForTask(task);
 
                 if (transform.visible) {
@@ -281,10 +327,10 @@
                 TaskView tv = (TaskView) getChildAt(i);
                 Task task = tv.getTask();
                 int taskIndex = mStack.indexOfTask(task);
-                if (taskIndex < 0 || !taskTransforms.get(taskIndex).visible) {
+                if (taskIndex < 0 || !mTaskTransforms.get(taskIndex).visible) {
                     mViewPool.returnViewToPool(tv);
                 } else {
-                    tv.updateViewPropertiesToTaskTransform(null, taskTransforms.get(taskIndex),
+                    tv.updateViewPropertiesToTaskTransform(null, mTaskTransforms.get(taskIndex),
                             mStackViewsAnimationDuration);
                 }
             }
@@ -361,7 +407,7 @@
         mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
         mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll -
                 curScroll, 250));
-        mScrollAnimator.setInterpolator(RecentsConfiguration.getInstance().fastOutSlowInInterpolator);
+        mScrollAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
         mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
@@ -543,48 +589,31 @@
     /** Enables the hw layers and increments the hw layer requirement ref count */
     void addHwLayersRefCount(String reason) {
         if (Console.Enabled) {
+            int refCount = mHwLayersTrigger.getCount();
             Console.log(Constants.Log.UI.HwLayers,
                     "[TaskStackView|addHwLayersRefCount] refCount: " +
-                            mHwLayersRefCount + "->" + (mHwLayersRefCount + 1) + " " + reason);
+                            refCount + "->" + (refCount + 1) + " " + reason);
         }
-        if (mHwLayersRefCount == 0) {
-            // Enable hw layers on each of the children
-            int childCount = getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                TaskView tv = (TaskView) getChildAt(i);
-                tv.enableHwLayers();
-            }
-        }
-        mHwLayersRefCount++;
+        mHwLayersTrigger.increment();
     }
 
     /** Decrements the hw layer requirement ref count and disables the hw layers when we don't
         need them anymore. */
     void decHwLayersRefCount(String reason) {
         if (Console.Enabled) {
+            int refCount = mHwLayersTrigger.getCount();
             Console.log(Constants.Log.UI.HwLayers,
                     "[TaskStackView|decHwLayersRefCount] refCount: " +
-                            mHwLayersRefCount + "->" + (mHwLayersRefCount - 1) + " " + reason);
+                            refCount + "->" + (refCount - 1) + " " + reason);
         }
-        mHwLayersRefCount--;
-        if (mHwLayersRefCount == 0) {
-            // Disable hw layers on each of the children
-            int childCount = getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                TaskView tv = (TaskView) getChildAt(i);
-                tv.disableHwLayers();
-            }
-        } else if (mHwLayersRefCount < 0) {
-            new Throwable("Invalid hw layers ref count").printStackTrace();
-            Console.logError(getContext(), "Invalid HW layers ref count");
-        }
+        mHwLayersTrigger.decrement();
     }
 
     @Override
     public void computeScroll() {
         if (mScroller.computeScrollOffset()) {
             setStackScroll(mScroller.getCurrY());
-            invalidate();
+            invalidate(mStackRect);
 
             // If we just finished scrolling, then disable the hw layers
             if (mScroller.isFinished()) {
@@ -616,7 +645,6 @@
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         if (Constants.DebugFlags.App.EnableTaskStackClipping) {
-            RecentsConfiguration config = RecentsConfiguration.getInstance();
             TaskView tv = (TaskView) child;
             TaskView nextTv = null;
             TaskView tmpTv = null;
@@ -632,13 +660,13 @@
                 }
 
                 // Clip against the next view (if we aren't animating its alpha)
-                if (nextTv != null && nextTv.getAlpha() == 1f) {
+                if (nextTv != null) {
                     Rect curRect = tv.getClippingRect(mTmpRect);
                     Rect nextRect = nextTv.getClippingRect(mTmpRect2);
                     // The hit rects are relative to the task view, which needs to be offset by
                     // the system bar height
-                    curRect.offset(0, config.systemInsets.top);
-                    nextRect.offset(0, config.systemInsets.top);
+                    curRect.offset(0, mConfig.systemInsets.top);
+                    nextRect.offset(0, mConfig.systemInsets.top);
                     // Compute the clip region
                     Region clipRegion = new Region();
                     clipRegion.op(curRect, Region.Op.UNION);
@@ -660,7 +688,6 @@
         // Note: We let the stack view be the full height because we want the cards to go under the
         //       navigation bar if possible.  However, the stack rects which we use to calculate
         //       max scroll, etc. need to take the nav bar into account
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
 
         // Compute the stack rects
         mRect.set(0, 0, width, height);
@@ -668,8 +695,8 @@
         mStackRect.left += insetLeft;
         mStackRect.bottom -= insetBottom;
 
-        int widthPadding = (int) (config.taskStackWidthPaddingPct * mStackRect.width());
-        int heightPadding = config.taskStackTopPaddingPx;
+        int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
+        int heightPadding = mConfig.taskStackTopPaddingPx;
         if (Constants.DebugFlags.App.EnableSearchLayout) {
             mStackRect.top += heightPadding;
             mStackRect.left += widthPadding;
@@ -707,10 +734,9 @@
         }
 
         // Compute our stack/task rects
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
         Rect taskStackBounds = new Rect();
-        config.getTaskStackBounds(width, height, taskStackBounds);
-        computeRects(width, height, taskStackBounds.left, config.systemInsets.bottom);
+        mConfig.getTaskStackBounds(width, height, taskStackBounds);
+        computeRects(width, height, taskStackBounds.left, mConfig.systemInsets.bottom);
 
         // Debug logging
         if (Constants.Log.UI.MeasureAndLayout) {
@@ -768,47 +794,66 @@
         }
 
         if (mAwaitingFirstLayout) {
-            RecentsConfiguration config = RecentsConfiguration.getInstance();
-
-            // Update the focused task index to be the next item to the top task
-            if (config.launchedFromAltTab) {
-                focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
-            }
-
-            // Prepare the first view for its enter animation
-            if (config.launchedWithThumbnailAnimation) {
-                TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
-                if (tv != null) {
-                    tv.prepareAnimateOnEnterRecents();
-                }
-            }
-
             // Mark that we have completely the first layout
             mAwaitingFirstLayout = false;
 
+            // Prepare the first view for its enter animation
+            int offsetTopAlign = -mTaskRect.top;
+            int offscreenY = mRect.bottom - (mTaskRect.top - mRect.top);
+            for (int i = childCount - 1; i >= 0; i--) {
+                TaskView tv = (TaskView) getChildAt(i);
+                tv.prepareAnimateEnterRecents((i == (getChildCount() - 1)), offsetTopAlign,
+                        offscreenY, mTaskRect);
+            }
+
             // If the enter animation started already and we haven't completed a layout yet, do the
             // enter animation now
             if (mStartEnterAnimationRequestedAfterLayout) {
-                startOnEnterAnimation();
+                startOnEnterAnimation(mStartEnterAnimationContext);
+                mStartEnterAnimationRequestedAfterLayout = false;
+                mStartEnterAnimationContext = null;
+            }
+
+            // Update the focused task index to be the next item to the top task
+            if (mConfig.launchedWithAltTab) {
+                focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
             }
         }
     }
 
     /** Requests this task stacks to start it's enter-recents animation */
-    public void startOnEnterAnimation() {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        if (!config.launchedWithThumbnailAnimation) return;
-
+    public void startOnEnterAnimation(ViewAnimation.TaskViewEnterContext ctx) {
         // If we are still waiting to layout, then just defer until then
         if (mAwaitingFirstLayout) {
             mStartEnterAnimationRequestedAfterLayout = true;
+            mStartEnterAnimationContext = ctx;
             return;
         }
 
-        // Animate the task bar of the first task view
-        TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
-        if (tv != null) {
-            tv.animateOnEnterRecents();
+        // Animate all the task views into view
+        ctx.taskRect = mTaskRect;
+        ctx.stackRectSansPeek = mStackRectSansPeek;
+        int childCount = getChildCount();
+        for (int i = childCount - 1; i >= 0; i--) {
+            TaskView tv = (TaskView) getChildAt(i);
+            TaskViewTransform transform = getStackTransform(mStack.indexOfTask(tv.getTask()),
+                    getStackScroll());
+            ctx.stackViewIndex = i;
+            ctx.stackViewCount = childCount;
+            ctx.isFrontMost = (i == (getChildCount() - 1));
+            ctx.transform = transform;
+            tv.animateOnEnterRecents(ctx);
+        }
+    }
+
+    /** Requests this task stacks to start it's exit-recents animation. */
+    public void startOnExitAnimation(ViewAnimation.TaskViewExitContext ctx) {
+        // Animate all the task views into view
+        ctx.offscreenTranslationY = mRect.bottom - (mTaskRect.top - mRect.top);
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            TaskView tv = (TaskView) getChildAt(i);
+            tv.animateOnExitRecents(ctx);
         }
     }
 
@@ -871,8 +916,7 @@
                         ArrayList<TaskViewTransform> curTaskTransforms,
                         ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms,
                         HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut,
-                        ArrayList<TaskView> childrenToRemoveOut,
-                        RecentsConfiguration config) {
+                        ArrayList<TaskView> childrenToRemoveOut) {
         // Animate all of the existing views out of view (if they are not in the visible range in
         // the new stack) or to their final positions in the new stack
         int movement = 0;
@@ -902,7 +946,7 @@
             childViewTransformsOut.put(tv, new Pair(0, toTransform));
         }
         return Utilities.calculateTranslationAnimationDuration(movement,
-                config.filteringCurrentViewsMinAnimDuration);
+                mConfig.filteringCurrentViewsMinAnimDuration);
     }
 
     /**
@@ -911,8 +955,7 @@
      */
     int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks,
                          ArrayList<TaskViewTransform> taskTransforms,
-                         HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut,
-                         RecentsConfiguration config) {
+                         HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut) {
         int offset = 0;
         int movement = 0;
         int taskCount = tasks.size();
@@ -942,7 +985,7 @@
             }
         }
         return Utilities.calculateTranslationAnimationDuration(movement,
-                config.filteringNewViewsMinAnimDuration);
+                mConfig.filteringNewViewsMinAnimDuration);
     }
 
     /** Orchestrates the animations of the current child views and any new views. */
@@ -950,22 +993,20 @@
                               ArrayList<TaskViewTransform> curTaskTransforms,
                               final ArrayList<Task> tasks,
                               final ArrayList<TaskViewTransform> taskTransforms) {
-        final RecentsConfiguration config = RecentsConfiguration.getInstance();
-
         // Calculate the transforms to animate out all the existing views if they are not in the
         // new visible range (or to their final positions in the stack if they are)
         final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>();
         final HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransforms =
                 new HashMap<TaskView, Pair<Integer, TaskViewTransform>>();
         int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks,
-                taskTransforms, childViewTransforms, childrenToRemove, config);
+                taskTransforms, childViewTransforms, childrenToRemove);
 
         // If all the current views are in the visible range of the new stack, then don't wait for
         // views to animate out and animate all the new views into their place
         final boolean unifyNewViewAnimation = childrenToRemove.isEmpty();
         if (unifyNewViewAnimation) {
             int inDuration = getEnterTransformsForFilterAnimation(tasks, taskTransforms,
-                    childViewTransforms, config);
+                    childViewTransforms);
             duration = Math.max(duration, inDuration);
         }
 
@@ -989,7 +1030,7 @@
                                     // For views that are not already visible, animate them in
                                     childViewTransforms.clear();
                                     int duration = getEnterTransformsForFilterAnimation(tasks,
-                                            taskTransforms, childViewTransforms, config);
+                                            taskTransforms, childViewTransforms);
                                     for (final TaskView tv : childViewTransforms.keySet()) {
                                         Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv);
                                         tv.animate().setStartDelay(t.first);
@@ -1127,7 +1168,7 @@
         }
 
         // Enable hw layers on this view if hw layers are enabled on the stack
-        if (mHwLayersRefCount > 0) {
+        if (mHwLayersTrigger.getCount() > 0) {
             tv.enableHwLayers();
         }
     }
@@ -1196,7 +1237,6 @@
 
     @Override
     public void onComponentRemoved(Set<ComponentName> cns) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
         // For other tasks, just remove them directly if they no longer exist
         ArrayList<Task> tasks = mStack.getTasks();
         for (int i = tasks.size() - 1; i >= 0; i--) {
@@ -1502,7 +1542,7 @@
                             mSv.mMinScroll, mSv.mMaxScroll,
                             0, overscrollRange);
                     // Invalidate to kick off computeScroll
-                    mSv.invalidate();
+                    mSv.invalidate(mSv.mStackRect);
                 } else if (mSv.isScrollOutOfBounds()) {
                     // Animate the scroll back into bounds
                     // XXX: Make this animation a function of the velocity OR distance
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 5df5e4d..9e4386f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -28,9 +28,11 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewParent;
 import android.view.animation.AccelerateInterpolator;
 import android.widget.FrameLayout;
 import com.android.systemui.R;
+import com.android.systemui.recents.Console;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.model.Task;
@@ -45,10 +47,10 @@
         public void onTaskAppInfoClicked(TaskView tv);
         public void onTaskFocused(TaskView tv);
         public void onTaskDismissed(TaskView tv);
-
-        // public void onTaskViewReboundToTask(TaskView tv, Task t);
     }
 
+    RecentsConfiguration mConfig;
+
     int mDim;
     int mMaxDim;
     TimeInterpolator mDimInterpolator = new AccelerateInterpolator();
@@ -59,11 +61,21 @@
     boolean mClipViewInStack;
     Point mLastTouchDown = new Point();
     Path mRoundedRectClipPath = new Path();
+    Rect mTmpRect = new Rect();
 
     TaskThumbnailView mThumbnailView;
     TaskBarView mBarView;
     TaskViewCallbacks mCb;
 
+    // Optimizations
+    ValueAnimator.AnimatorUpdateListener mUpdateDimListener =
+            new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    updateDimOverlayFromScale();
+                }
+            };
+
 
     public TaskView(Context context) {
         this(context, null);
@@ -79,13 +91,13 @@
 
     public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        mConfig = RecentsConfiguration.getInstance();
         setWillNotDraw(false);
     }
 
     @Override
     protected void onFinishInflate() {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        mMaxDim = config.taskStackMaxDim;
+        mMaxDim = mConfig.taskStackMaxDim;
 
         // By default, all views are clipped to other views in their stack
         mClipViewInStack = true;
@@ -104,8 +116,7 @@
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
         // Update the rounded rect clip path
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        float radius = config.taskViewRoundedCornerRadiusPx;
+        float radius = mConfig.taskViewRoundedCornerRadiusPx;
         mRoundedRectClipPath.reset();
         mRoundedRectClipPath.addRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()),
                 radius, radius, Path.Direction.CW);
@@ -113,7 +124,7 @@
         // Update the outline
         Outline o = new Outline();
         o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight() -
-                config.taskViewShadowOutlineBottomInsetPx, radius);
+                mConfig.taskViewShadowOutlineBottomInsetPx, radius);
         setOutline(o);
     }
 
@@ -141,7 +152,10 @@
     /** Synchronizes this view's properties with the task's transform */
     void updateViewPropertiesToTaskTransform(TaskViewTransform animateFromTransform,
                                              TaskViewTransform toTransform, int duration) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
+        if (Console.Enabled) {
+            Console.log(Constants.Log.UI.Draw, "[TaskView|updateViewPropertiesToTaskTransform]",
+                    "duration: " + duration, Console.AnsiPurple);
+        }
 
         // Update the bar view
         mBarView.updateViewPropertiesToTaskTransform(animateFromTransform, toTransform, duration);
@@ -164,15 +178,10 @@
                     .scaleX(toTransform.scale)
                     .scaleY(toTransform.scale)
                     .alpha(toTransform.alpha)
+                    .setStartDelay(0)
                     .setDuration(duration)
-                    .setInterpolator(config.fastOutSlowInInterpolator)
-                    .withLayer()
-                    .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                        @Override
-                        public void onAnimationUpdate(ValueAnimator animation) {
-                            updateDimOverlayFromScale();
-                        }
-                    })
+                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
+                    .setUpdateListener(mUpdateDimListener)
                     .start();
         } else {
             setTranslationY(toTransform.translationY);
@@ -197,6 +206,7 @@
         setScaleX(1f);
         setScaleY(1f);
         setAlpha(1f);
+        mDim = 0;
         invalidate();
     }
 
@@ -221,40 +231,103 @@
 
     /** Prepares this task view for the enter-recents animations.  This is called earlier in the
      * first layout because the actual animation into recents may take a long time. */
-    public void prepareAnimateOnEnterRecents() {
-        mBarView.setVisibility(View.INVISIBLE);
+    public void prepareAnimateEnterRecents(boolean isTaskViewFrontMost, int offsetY, int offscreenY,
+                                           Rect taskRect) {
+        if (mConfig.launchedFromAppWithScreenshot) {
+            if (isTaskViewFrontMost) {
+                // Hide the task view as we are going to animate the full screenshot into view
+                // and then replace it with this view once we are done
+                setVisibility(View.INVISIBLE);
+                // Also hide the front most task bar view so we can animate it in
+                mBarView.prepareAnimateEnterRecents();
+            } else {
+                // Top align the task views
+                setTranslationY(offsetY);
+                setScaleX(1f);
+                setScaleY(1f);
+            }
+
+        } else if (mConfig.launchedFromAppWithThumbnail) {
+            if (isTaskViewFrontMost) {
+                // Hide the front most task bar view so we can animate it in
+                mBarView.prepareAnimateEnterRecents();
+            }
+
+        } else if (mConfig.launchedFromHome) {
+            // Move the task view off screen (below) so we can animate it in
+            setTranslationY(offscreenY);
+            setScaleX(1f);
+            setScaleY(1f);
+        }
     }
 
     /** Animates this task view as it enters recents */
-    public void animateOnEnterRecents() {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        mBarView.setVisibility(View.VISIBLE);
-        mBarView.setTranslationY(-mBarView.getMeasuredHeight());
-        mBarView.animate()
-                .translationY(0)
-                .setStartDelay(config.taskBarEnterAnimDelay)
-                .setInterpolator(config.fastOutSlowInInterpolator)
-                .setDuration(config.taskBarEnterAnimDuration)
+    public void animateOnEnterRecents(ViewAnimation.TaskViewEnterContext ctx) {
+        TaskViewTransform transform = ctx.transform;
+
+        if (mConfig.launchedFromAppWithScreenshot) {
+            if (ctx.isFrontMost) {
+                // Animate the full screenshot down first, before swapping with this task view
+                ctx.fullScreenshot.animateOnEnterRecents(ctx, new Runnable() {
+                    @Override
+                    public void run() {
+                        // Animate the task bar of the first task view
+                        mBarView.animateOnEnterRecents(0);
+                        setVisibility(View.VISIBLE);
+                    }
+                });
+            } else {
+                // Animate the tasks down behind the full screenshot
+                animate()
+                        .scaleX(transform.scale)
+                        .scaleY(transform.scale)
+                        .translationY(transform.translationY)
+                        .setStartDelay(0)
+                        .setInterpolator(mConfig.linearOutSlowInInterpolator)
+                        .setDuration(475)
+                        .withLayer()
+                        .start();
+            }
+
+        } else if (mConfig.launchedFromAppWithThumbnail) {
+            if (ctx.isFrontMost) {
+                // Animate the task bar of the first task view
+                mBarView.animateOnEnterRecents(-1);
+            }
+
+        } else if (mConfig.launchedFromHome) {
+            // Animate the tasks up
+            int frontIndex = (ctx.stackViewCount - ctx.stackViewIndex - 1);
+            int delay = mConfig.taskBarEnterAnimDelay +
+                    frontIndex * mConfig.taskViewEnterFromHomeDelay;
+            animate()
+                    .scaleX(transform.scale)
+                    .scaleY(transform.scale)
+                    .translationY(transform.translationY)
+                    .setStartDelay(delay)
+                    .setInterpolator(mConfig.quintOutInterpolator)
+                    .setDuration(mConfig.taskViewEnterFromHomeDuration)
+                    .withLayer()
+                    .start();
+        }
+    }
+
+    /** Animates this task view as it leaves recents */
+    public void animateOnExitRecents(ViewAnimation.TaskViewExitContext ctx) {
+        animate()
+                .translationY(ctx.offscreenTranslationY)
+                .setStartDelay(0)
+                .setInterpolator(mConfig.fastOutSlowInInterpolator)
+                .setDuration(mConfig.taskViewEnterFromHomeDuration)
                 .withLayer()
+                .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
                 .start();
+        ctx.postAnimationTrigger.increment();
     }
 
     /** Animates this task view as it exits recents */
-    public void animateOnLeavingRecents(final Runnable r) {
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        mBarView.animate()
-            .translationY(-mBarView.getMeasuredHeight())
-            .setStartDelay(0)
-            .setInterpolator(config.fastOutLinearInInterpolator)
-            .setDuration(config.taskBarExitAnimDuration)
-            .withLayer()
-            .withEndAction(new Runnable() {
-                @Override
-                public void run() {
-                    post(r);
-                }
-            })
-            .start();
+    public void animateOnLaunchingTask(final Runnable r) {
+        mBarView.animateOnLaunchingTask(r);
     }
 
     /** Animates the deletion of this task view */
@@ -262,20 +335,24 @@
         // Disabling clipping with the stack while the view is animating away
         setClipViewInStack(false);
 
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        animate().translationX(config.taskViewRemoveAnimTranslationXPx)
+        animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx)
             .alpha(0f)
             .setStartDelay(0)
-            .setInterpolator(config.fastOutSlowInInterpolator)
-            .setDuration(config.taskViewRemoveAnimDuration)
+            .setInterpolator(mConfig.fastOutSlowInInterpolator)
+            .setDuration(mConfig.taskViewRemoveAnimDuration)
             .withLayer()
             .withEndAction(new Runnable() {
                 @Override
                 public void run() {
-                    post(r);
+                    // We just throw this into a runnable because starting a view property
+                    // animation using layers can cause inconsisten results if we try and
+                    // update the layers while the animation is running.  In some cases,
+                    // the runnabled passed in may start an animation which also uses layers
+                    // so we defer all this by posting this.
+                    r.run();
 
                     // Re-enable clipping with the stack (we will reuse this view)
-                    setClipViewInStack(false);
+                    setClipViewInStack(true);
                 }
             })
             .start();
@@ -293,11 +370,13 @@
     /** Enable the hw layers on this task view */
     void enableHwLayers() {
         mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+        mBarView.enableHwLayers();
     }
 
     /** Disable the hw layers on this task view */
     void disableHwLayers() {
         mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null);
+        mBarView.disableHwLayers();
     }
 
     /**
@@ -305,7 +384,7 @@
      * view.
      */
     boolean shouldClipViewInStack() {
-        return mClipViewInStack;
+        return mClipViewInStack && (getVisibility() == View.VISIBLE);
     }
 
     /** Sets whether this view should be clipped, or clipped against. */
@@ -313,9 +392,8 @@
         if (clip != mClipViewInStack) {
             mClipViewInStack = clip;
             if (getParent() instanceof View) {
-                Rect r = new Rect();
-                getHitRect(r);
-                ((View) getParent()).invalidate(r);
+                getHitRect(mTmpRect);
+                ((View) getParent()).invalidate(mTmpRect);
             }
         }
     }
@@ -391,8 +469,7 @@
             mBarView.mApplicationIcon.setOnClickListener(this);
             mBarView.mDismissButton.setOnClickListener(this);
             if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
-                RecentsConfiguration config = RecentsConfiguration.getInstance();
-                if (config.developerOptionsEnabled) {
+                if (mConfig.developerOptionsEnabled) {
                     mBarView.mApplicationIcon.setOnLongClickListener(this);
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index 3c3ebd7..4a76872 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -47,7 +47,8 @@
 
     @Override
     public String toString() {
-        return "TaskViewTransform y: " + translationY + " scale: " + scale + " alpha: " + alpha +
-                " visible: " + visible + " rect: " + rect + " dismissAlpha: " + dismissAlpha;
+        return "TaskViewTransform y: " + translationY + " z: " + translationZ + " scale: " + scale +
+                " alpha: " + alpha + " visible: " + visible + " rect: " + rect +
+                " dismissAlpha: " + dismissAlpha;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
new file mode 100644
index 0000000..b5e8ffd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.graphics.Rect;
+import com.android.systemui.recents.ReferenceCountedTrigger;
+
+/* Common code related to view animations */
+public class ViewAnimation {
+
+    /* The animation context for a task view animation into Recents */
+    public static class TaskViewEnterContext {
+        // The full screenshot view that we are animating down
+        FullScreenTransitionView fullScreenshot;
+        // The transform of the current task view
+        TaskViewTransform transform;
+        // The stack rect that the transform is relative to
+        Rect stackRectSansPeek;
+        // The task rect
+        Rect taskRect;
+        // The view index of the current task view
+        int stackViewIndex;
+        // The total number of task views
+        int stackViewCount;
+        // Whether this is the front most task view
+        boolean isFrontMost;
+
+        public TaskViewEnterContext(FullScreenTransitionView fss) {
+            fullScreenshot = fss;
+        }
+    }
+
+    /* The animation context for a task view animation out of Recents */
+    public static class TaskViewExitContext {
+        // A trigger to run some logic when all the animations complete.  This works around the fact
+        // that it is difficult to coordinate ViewPropertyAnimators
+        ReferenceCountedTrigger postAnimationTrigger;
+        // The translationY to apply to a TaskView to move it off screen
+        int offscreenTranslationY;
+
+        public TaskViewExitContext(ReferenceCountedTrigger t) {
+            postAnimationTrigger = t;
+        }
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java b/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
index d67e7cb..a3b10f2 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
@@ -21,8 +21,16 @@
 import android.app.ActivityManagerNative;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -43,15 +51,18 @@
 /**
  * A quick and dirty view to show a user switcher.
  */
-public class UserSwitcherHostView extends FrameLayout implements ListView.OnItemClickListener {
+public class UserSwitcherHostView extends FrameLayout
+        implements ListView.OnItemClickListener, View.OnClickListener {
 
     private static final String TAG = "UserSwitcherDialog";
 
     private ArrayList<UserInfo> mUserInfo = new ArrayList<UserInfo>();
+    private UserInfo mGuestUser;
     private Adapter mAdapter = new Adapter();
     private UserManager mUserManager;
     private Runnable mFinishRunnable;
     private ListView mListView;
+    private boolean mGuestUserEnabled;
 
     public UserSwitcherHostView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
@@ -60,6 +71,9 @@
             return;
         }
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+
+        mGuestUserEnabled = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.GUEST_USER_ENABLED, 0) == 1;
     }
 
     public UserSwitcherHostView(Context context, AttributeSet attrs) {
@@ -80,7 +94,39 @@
 
     @Override
     public void onItemClick(AdapterView<?> l, View v, int position, long id) {
-        int userId = mAdapter.getItem(position).id;
+        // Last item is the guest
+        if (position == mUserInfo.size()) {
+            postDelayed(new Runnable() {
+                public void run() {
+                    switchToGuestUser();
+                }
+            }, 100);
+        } else {
+            final int userId = mAdapter.getItem(position).id;
+            postDelayed(new Runnable() {
+                public void run() {
+                    switchUser(userId);
+                }
+            }, 100);
+        }
+    }
+
+    @Override
+    public void onClick(View v) {
+        // Delete was clicked
+        postDelayed(new Runnable() {
+            public void run() {
+                if (mGuestUser != null) {
+                    switchUser(0);
+                    mUserManager.removeUser(mGuestUser.id);
+                    mGuestUser = null;
+                    refreshUsers();
+                }
+            }
+        }, 100);
+    }
+
+    private void switchUser(int userId) {
         try {
             WindowManagerGlobal.getWindowManagerService().lockNow(null);
             ActivityManagerNative.getDefault().switchUser(userId);
@@ -90,6 +136,15 @@
         }
     }
 
+    private void switchToGuestUser() {
+        if (mGuestUser == null) {
+            // No guest user. Create one.
+            mGuestUser = mUserManager.createGuest(mContext, 
+                    mContext.getResources().getString(R.string.guest_nickname));
+        }
+        switchUser(mGuestUser.id);
+    }
+
     private void finish() {
         if (mFinishRunnable != null) {
             mFinishRunnable.run();
@@ -119,9 +174,12 @@
 
     public void refreshUsers() {
         mUserInfo.clear();
+        mGuestUser = null;
         List<UserInfo> users = mUserManager.getUsers(true);
         for (UserInfo user : users) {
-            if (!user.isManagedProfile()) {
+            if (user.isGuest()) {
+                mGuestUser = user;
+            } else if (!user.isManagedProfile()) {
                 mUserInfo.add(user);
             }
         }
@@ -132,17 +190,25 @@
 
         @Override
         public int getCount() {
-            return mUserInfo.size();
+            return mUserInfo.size() + (mGuestUserEnabled ? 1 : 0);
         }
 
         @Override
         public UserInfo getItem(int position) {
-            return mUserInfo.get(position);
+            if (position < mUserInfo.size()) {
+                return mUserInfo.get(position);
+            } else {
+                return mGuestUser;
+            }
         }
 
         @Override
         public long getItemId(int position) {
-            return getItem(position).serialNumber;
+            if (position < mUserInfo.size()) {
+                return getItem(position).serialNumber;
+            } else {
+                return mGuestUser != null ? mGuestUser.serialNumber : -1;
+            }
         }
 
         @Override
@@ -161,18 +227,46 @@
             ViewHolder h = new ViewHolder();
             h.name = (TextView) v.findViewById(R.id.user_name);
             h.picture = (ImageView) v.findViewById(R.id.user_picture);
+            h.delete = (ImageView) v.findViewById(R.id.user_delete);
             v.setTag(h);
             return v;
         }
 
         private void bindView(ViewHolder h, UserInfo item) {
-            h.name.setText(item.name);
-            h.picture.setImageBitmap(mUserManager.getUserIcon(item.id));
+            if (item != null) {
+                h.name.setText(item.name);
+                h.picture.setImageBitmap(circularClip(mUserManager.getUserIcon(item.id)));
+                h.delete.setVisibility(item.isGuest() ? View.VISIBLE : View.GONE);
+                h.delete.setOnClickListener(UserSwitcherHostView.this);
+                if (item.isGuest()) {
+                    h.picture.setImageResource(R.drawable.ic_account_circle);
+                }
+            } else {
+                h.name.setText(R.string.guest_new_guest);
+                h.picture.setImageResource(R.drawable.ic_account_circle);
+                h.delete.setVisibility(View.GONE);
+            }
+        }
+
+        private Bitmap circularClip(Bitmap input) {
+            if (input == null) {
+                return null;
+            }
+            Bitmap output = Bitmap.createBitmap(input.getWidth(),
+                    input.getHeight(), Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(output);
+            final Paint paint = new Paint();
+            paint.setShader(new BitmapShader(input, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
+            paint.setAntiAlias(true);
+            canvas.drawCircle(input.getWidth() / 2, input.getHeight() / 2, input.getWidth() / 2,
+                    paint);
+            return output;
         }
 
         class ViewHolder {
             TextView name;
             ImageView picture;
+            ImageView delete;
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 7196b62..506f9dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -89,7 +89,8 @@
 import static com.android.keyguard.KeyguardHostView.OnDismissAction;
 
 public abstract class BaseStatusBar extends SystemUI implements
-        CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener {
+        CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener,
+        RecentsComponent.Callbacks {
     public static final String TAG = "StatusBar";
     public static final boolean DEBUG = false;
     public static final boolean MULTIUSER_DEBUG = false;
@@ -386,6 +387,7 @@
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
 
         mRecents = getComponent(RecentsComponent.class);
+        mRecents.setCallback(this);
 
         mLocale = mContext.getResources().getConfiguration().locale;
         mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
@@ -769,6 +771,11 @@
         }
     }
 
+    @Override
+    public void onVisibilityChanged(boolean visible) {
+        // Do nothing
+    }
+
     public abstract void resetHeadsUpDecayTimer();
 
     public abstract void scheduleHeadsUpOpen();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index d413f63..a2dbf75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -267,7 +267,6 @@
     private TextView mCarrierLabel;
     private boolean mCarrierLabelVisible = false;
     private int mCarrierLabelHeight;
-    private TextView mEmergencyCallLabel;
     private int mStatusBarHeaderHeight;
 
     private boolean mShowCarrierInPanel = false;
@@ -717,23 +716,9 @@
 
         mNetworkController.addSignalCluster(signalCluster);
         signalCluster.setNetworkController(mNetworkController);
-
         final boolean isAPhone = mNetworkController.hasVoiceCallingFeature();
         if (isAPhone) {
-            mEmergencyCallLabel =
-                    (TextView) mStatusBarWindow.findViewById(R.id.emergency_calls_only);
-            // TODO: Uncomment when correctly positioned
-//            if (mEmergencyCallLabel != null) {
-//                mNetworkController.addEmergencyLabelView(mEmergencyCallLabel);
-//                mEmergencyCallLabel.setOnClickListener(new View.OnClickListener() {
-//                    public void onClick(View v) { }});
-//                mEmergencyCallLabel.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
-//                    @Override
-//                    public void onLayoutChange(View v, int left, int top, int right, int bottom,
-//                            int oldLeft, int oldTop, int oldRight, int oldBottom) {
-//                        updateCarrierLabelVisibility(false);
-//                    }});
-//            }
+            mNetworkController.addEmergencyLabelView(mHeader);
         }
 
         mCarrierLabel = (TextView)mStatusBarWindow.findViewById(R.id.carrier_label);
@@ -759,6 +744,8 @@
 //                    updateCarrierLabelVisibility(false);
         }
 
+        mBatteryController.setStatusBarHeaderView(mHeader);
+
         // Set up the quick settings tile panel
         mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel);
         if (mQSPanel != null) {
@@ -1389,7 +1376,8 @@
                     mCarrierLabelHeight));
         }
 
-        final boolean emergencyCallsShownElsewhere = mEmergencyCallLabel != null;
+        // Emergency calls only is shown in the expanded header now.
+        final boolean emergencyCallsShownElsewhere = true;
         final boolean makeVisible =
             !(emergencyCallsShownElsewhere && mNetworkController.isEmergencyOnly())
             && mStackScroller.getHeight() < (mNotificationPanel.getHeight()
@@ -1720,7 +1708,6 @@
         }
 
         if (mStatusBarWindow != null) {
-
             // release focus immediately to kick off focus change transition
             mStatusBarWindowManager.setStatusBarFocusable(false);
 
@@ -1962,6 +1949,10 @@
                 Integer.toHexString(oldVal), Integer.toHexString(newVal),
                 Integer.toHexString(diff)));
         if (diff != 0) {
+            // we never set the recents bit via this method, so save the prior state to prevent
+            // clobbering the bit below
+            final boolean wasRecentsVisible = (mSystemUiVisibility & View.RECENT_APPS_VISIBLE) > 0;
+
             mSystemUiVisibility = newVal;
 
             // update low profile
@@ -2016,6 +2007,11 @@
                 mSystemUiVisibility &= ~View.NAVIGATION_BAR_UNHIDE;
             }
 
+            // restore the recents bit
+            if (wasRecentsVisible) {
+                mSystemUiVisibility |= View.RECENT_APPS_VISIBLE;
+            }
+
             // send updated sysui visibility to window manager
             notifyUiVisibilityChanged(mSystemUiVisibility);
         }
@@ -3146,4 +3142,41 @@
             }
         }
     };
+
+    // Recents
+
+    @Override
+    protected void showRecents(boolean triggeredFromAltTab) {
+        // Set the recents visibility flag
+        mSystemUiVisibility |= View.RECENT_APPS_VISIBLE;
+        notifyUiVisibilityChanged(mSystemUiVisibility);
+        super.showRecents(triggeredFromAltTab);
+    }
+
+    @Override
+    protected void hideRecents(boolean triggeredFromAltTab) {
+        // Unset the recents visibility flag
+        mSystemUiVisibility &= ~View.RECENT_APPS_VISIBLE;
+        notifyUiVisibilityChanged(mSystemUiVisibility);
+        super.hideRecents(triggeredFromAltTab);
+    }
+
+    @Override
+    protected void toggleRecents() {
+        // Toggle the recents visibility flag
+        mSystemUiVisibility ^= View.RECENT_APPS_VISIBLE;
+        notifyUiVisibilityChanged(mSystemUiVisibility);
+        super.toggleRecents();
+    }
+
+    @Override
+    public void onVisibilityChanged(boolean visible) {
+        // Update the recents visibility flag
+        if (visible) {
+            mSystemUiVisibility |= View.RECENT_APPS_VISIBLE;
+        } else {
+            mSystemUiVisibility &= ~View.RECENT_APPS_VISIBLE;
+        }
+        notifyUiVisibilityChanged(mSystemUiVisibility);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index 7837769..c8ab027 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -26,6 +26,7 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
+import android.widget.TextView;
 
 import com.android.systemui.R;
 import com.android.systemui.qs.QSPanel;
@@ -57,6 +58,11 @@
     private View mSignalCluster;
     private View mSettingsButton;
     private View mBrightnessContainer;
+    private View mEmergencyCallsOnly;
+    private TextView mChargingInfo;
+
+    private boolean mShowEmergencyCallsOnly;
+    private boolean mShowChargingInfo;
 
     private int mCollapsedHeight;
     private int mExpandedHeight;
@@ -64,8 +70,6 @@
 
     private int mKeyguardWidth = ViewGroup.LayoutParams.MATCH_PARENT;
     private int mNormalWidth;
-    private int mPadding;
-    private int mMultiUserExpandedMargin;
 
     private ActivityStarter mActivityStarter;
     private BrightnessController mBrightnessController;
@@ -93,6 +97,8 @@
         mBrightnessController = new BrightnessController(getContext(),
                 (ImageView) findViewById(R.id.brightness_icon),
                 (ToggleSlider) findViewById(R.id.brightness_slider));
+        mEmergencyCallsOnly = findViewById(R.id.header_emergency_calls_only);
+        mChargingInfo = (TextView) findViewById(R.id.header_charging_info);
         loadDimens();
         updateVisibilities();
         addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@@ -114,10 +120,6 @@
         mKeyguardHeight = getResources().getDimensionPixelSize(
                 R.dimen.status_bar_header_height_keyguard);
         mNormalWidth = getLayoutParams().width;
-        mPadding = getResources().getDimensionPixelSize(R.dimen.notification_side_padding);
-        mMultiUserExpandedMargin =
-                getResources().getDimensionPixelSize(R.dimen.multi_user_switch_expanded_margin);
-
     }
 
     public void setActivityStarter(ActivityStarter activityStarter) {
@@ -145,8 +147,6 @@
             updateZTranslation();
             updateClickTargets();
             updateWidth();
-            updatePadding();
-            updateMultiUserSwitch();
             if (mQSPanel != null) {
                 mQSPanel.setExpanded(expanded && !overscrolled);
             }
@@ -204,13 +204,32 @@
         if (mSignalCluster != null) {
             mSignalCluster.setVisibility(!mExpanded || mOverscrolled ? View.VISIBLE : View.GONE);
         }
+        mEmergencyCallsOnly.setVisibility(mExpanded && !mOverscrolled && mShowEmergencyCallsOnly
+                ? VISIBLE : GONE);
+        mChargingInfo.setVisibility(mExpanded && !mOverscrolled && mShowChargingInfo
+                && !mShowEmergencyCallsOnly ? VISIBLE : GONE);
     }
 
     private void updateSystemIconsLayoutParams() {
         RelativeLayout.LayoutParams lp = (LayoutParams) mSystemIconsContainer.getLayoutParams();
-        lp.addRule(RelativeLayout.START_OF, mExpanded
-                ? mSettingsButton.getId()
-                : mMultiUserSwitch.getId());
+        boolean systemIconsAboveClock = mExpanded && !mOverscrolled
+                && mShowChargingInfo && !mShowEmergencyCallsOnly;
+        if (systemIconsAboveClock) {
+            lp.addRule(ALIGN_PARENT_START);
+            lp.removeRule(START_OF);
+        } else {
+            lp.addRule(RelativeLayout.START_OF, mExpanded
+                    ? mSettingsButton.getId()
+                    : mMultiUserSwitch.getId());
+            lp.removeRule(ALIGN_PARENT_START);
+        }
+
+        RelativeLayout.LayoutParams clockLp = (LayoutParams) mDateTime.getLayoutParams();
+        if (systemIconsAboveClock) {
+            clockLp.addRule(BELOW, mChargingInfo.getId());
+        } else {
+            clockLp.addRule(BELOW, mEmergencyCallsOnly.getId());
+        }
     }
 
     private void updateBrightnessControllerState() {
@@ -237,21 +256,6 @@
         }
     }
 
-    private void updatePadding() {
-        boolean padded = !mKeyguardShowing || mExpanded;
-        int padding = padded ? mPadding : 0;
-        setPaddingRelative(padding, 0, padding, 0);
-    }
-
-    private void updateMultiUserSwitch() {
-        int marginEnd = !mKeyguardShowing || mExpanded ? mMultiUserExpandedMargin : 0;
-        MarginLayoutParams lp = (MarginLayoutParams) mMultiUserSwitch.getLayoutParams();
-        if (marginEnd != lp.getMarginEnd()) {
-            lp.setMarginEnd(marginEnd);
-            mMultiUserSwitch.setLayoutParams(lp);
-        }
-    }
-
     public void setExpansion(float height) {
         height = (height - mCollapsedHeight) * EXPANSION_RUBBERBAND_FACTOR + mCollapsedHeight;
         if (height < mCollapsedHeight) {
@@ -297,8 +301,6 @@
         updateWidth();
         updateVisibilities();
         updateZTranslation();
-        updatePadding();
-        updateMultiUserSwitch();
     }
 
     public void setUserInfoController(UserInfoController userInfoController) {
@@ -338,4 +340,24 @@
     public boolean shouldDelayChildPressedState() {
         return true;
     }
+
+    public void setShowEmergencyCallsOnly(boolean show) {
+        mShowEmergencyCallsOnly = show;
+        if (mExpanded) {
+            updateVisibilities();
+            updateSystemIconsLayoutParams();
+        }
+    }
+
+    public void setShowChargingInfo(boolean showChargingInfo) {
+        mShowChargingInfo = showChargingInfo;
+        if (mExpanded) {
+            updateVisibilities();
+            updateSystemIconsLayoutParams();
+        }
+    }
+
+    public void setChargingInfo(CharSequence chargingInfo) {
+        mChargingInfo.setText(chargingInfo);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 6db9bc3..4cf66a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -16,26 +16,49 @@
 
 package com.android.systemui.statusbar.policy;
 
+import com.android.internal.app.IBatteryStats;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.StatusBarHeaderView;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.BatteryManager;
+import android.os.BatteryStats;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.text.format.Formatter;
+import android.util.Log;
 
 import java.util.ArrayList;
 
 public class BatteryController extends BroadcastReceiver {
     private static final String TAG = "StatusBar.BatteryController";
 
-
     private ArrayList<BatteryStateChangeCallback> mChangeCallbacks =
             new ArrayList<BatteryStateChangeCallback>();
 
+    private Context mContext;
+    private StatusBarHeaderView mStatusBarHeaderView;
+    private IBatteryStats mBatteryInfo;
+
+    private int mLevel;
+    private boolean mPluggedIn;
+    private boolean mCharging;
+    private boolean mCharged;
+
+
     public interface BatteryStateChangeCallback {
-        public void onBatteryLevelChanged(int level, boolean pluggedIn);
+        public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging);
     }
 
     public BatteryController(Context context) {
+        mContext = context;
+
+        mBatteryInfo = IBatteryStats.Stub.asInterface(
+                ServiceManager.getService(BatteryStats.SERVICE_NAME));
+
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
         context.registerReceiver(this, filter);
@@ -45,24 +68,59 @@
         mChangeCallbacks.add(cb);
     }
 
+    public void setStatusBarHeaderView(StatusBarHeaderView statusBarHeaderView) {
+        mStatusBarHeaderView = statusBarHeaderView;
+        updateStatusBarHeaderView();
+    }
+
     public void onReceive(Context context, Intent intent) {
         final String action = intent.getAction();
         if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
-            final int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
+            mLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
+            mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
+
             final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
                     BatteryManager.BATTERY_STATUS_UNKNOWN);
+            mCharged = status == BatteryManager.BATTERY_STATUS_FULL;
+            mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING;
 
-            boolean plugged = false;
-            switch (status) {
-                case BatteryManager.BATTERY_STATUS_CHARGING:
-                case BatteryManager.BATTERY_STATUS_FULL:
-                    plugged = true;
-                    break;
-            }
-
+            updateStatusBarHeaderView();
             for (BatteryStateChangeCallback cb : mChangeCallbacks) {
-                cb.onBatteryLevelChanged(level, plugged);
+                cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
             }
         }
     }
+
+    private void updateStatusBarHeaderView() {
+        if (mStatusBarHeaderView != null) {
+            mStatusBarHeaderView.setShowChargingInfo(mPluggedIn);
+            mStatusBarHeaderView.setChargingInfo(computeChargingInfo());
+        }
+    }
+
+    private String computeChargingInfo() {
+        if (!mPluggedIn || !mCharged && !mCharging) {
+            return mContext.getResources().getString(R.string.expanded_header_battery_not_charging);
+        }
+
+        if (mCharged) {
+            return mContext.getResources().getString(R.string.expanded_header_battery_charged);
+        }
+
+        // Try fetching charging time from battery stats.
+        try {
+            long chargingTimeRemaining = mBatteryInfo.computeChargeTimeRemaining();
+            if (chargingTimeRemaining > 0) {
+                String chargingTimeFormatted =
+                        Formatter.formatShortElapsedTime(mContext, chargingTimeRemaining);
+                return mContext.getResources().getString(
+                        R.string.expanded_header_battery_charging_with_time, chargingTimeFormatted);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling IBatteryStats: ", e);
+        }
+
+        // Fall back to simple charging label.
+        return mContext.getResources().getString(R.string.expanded_header_battery_charging);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
index cadb44a..f978833 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.text.format.DateFormat;
 import android.util.AttributeSet;
 import android.widget.TextView;
 
@@ -29,8 +30,6 @@
 import java.util.Date;
 import java.util.Locale;
 
-import libcore.icu.ICU;
-
 public class DateView extends TextView {
     private static final String TAG = "DateView";
 
@@ -87,7 +86,7 @@
         if (mDateFormat == null) {
             final String dateFormat = getContext().getString(R.string.system_ui_date_pattern);
             final Locale l = Locale.getDefault();
-            final String fmt = ICU.getBestDateTimePattern(dateFormat, l.toString());
+            final String fmt = DateFormat.getBestDateTimePattern(l, dateFormat);
             mDateFormat = new SimpleDateFormat(fmt, l);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 254a0e8..4e54e41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -47,6 +47,7 @@
 import com.android.internal.util.AsyncChannel;
 import com.android.systemui.DemoMode;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.StatusBarHeaderView;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -144,7 +145,7 @@
     ArrayList<TextView> mCombinedLabelViews = new ArrayList<TextView>();
     ArrayList<TextView> mMobileLabelViews = new ArrayList<TextView>();
     ArrayList<TextView> mWifiLabelViews = new ArrayList<TextView>();
-    ArrayList<TextView> mEmergencyLabelViews = new ArrayList<TextView>();
+    ArrayList<StatusBarHeaderView> mEmergencyViews = new ArrayList<>();
     ArrayList<SignalCluster> mSignalClusters = new ArrayList<SignalCluster>();
     ArrayList<NetworkSignalChangedCallback> mSignalsChangedCallbacks =
             new ArrayList<NetworkSignalChangedCallback>();
@@ -262,8 +263,8 @@
         mWifiLabelViews.add(v);
     }
 
-    public void addEmergencyLabelView(TextView v) {
-        mEmergencyLabelViews.add(v);
+    public void addEmergencyLabelView(StatusBarHeaderView v) {
+        mEmergencyViews.add(v);
     }
 
     public void addSignalCluster(SignalCluster cluster) {
@@ -1254,15 +1255,10 @@
         }
 
         // e-call label
-        N = mEmergencyLabelViews.size();
+        N = mEmergencyViews.size();
         for (int i=0; i<N; i++) {
-            TextView v = mEmergencyLabelViews.get(i);
-            if (!emergencyOnly) {
-                v.setVisibility(View.GONE);
-            } else {
-                v.setText(mobileLabel); // comes from the telephony stack
-                v.setVisibility(View.VISIBLE);
-            }
+            StatusBarHeaderView v = mEmergencyViews.get(i);
+            v.setShowEmergencyCallsOnly(emergencyOnly);
         }
     }
 
diff --git a/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java b/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java
index 764156d..66ece4f 100644
--- a/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java
+++ b/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java
@@ -283,9 +283,6 @@
             } catch (FileNotFoundException e) {
                 Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
                 return null;
-            } catch (IOException e) {
-                Log.e("BitmapRegionTileSource", "Failure while reading URI " + mUri, e);
-                return null;
             }
         }
         @Override
diff --git a/packages/services/PacProcessor/Android.mk b/packages/services/PacProcessor/Android.mk
index 79f8a1f..3c4e951 100644
--- a/packages/services/PacProcessor/Android.mk
+++ b/packages/services/PacProcessor/Android.mk
@@ -27,8 +27,6 @@
 
 LOCAL_JNI_SHARED_LIBRARIES := libjni_pacprocessor
 
-LOCAL_MULTILIB := 32
-
 include $(BUILD_PACKAGE)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/packages/services/PacProcessor/jni/Android.mk b/packages/services/PacProcessor/jni/Android.mk
index 8a60927..f16c90b 100644
--- a/packages/services/PacProcessor/jni/Android.mk
+++ b/packages/services/PacProcessor/jni/Android.mk
@@ -35,7 +35,6 @@
 
 LOCAL_MODULE := libjni_pacprocessor
 LOCAL_MODULE_TAGS := optional
-LOCAL_32_BIT_ONLY := true
 
 include external/stlport/libstlport.mk
 
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index ef096e0..5e1aa3b 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -119,6 +119,9 @@
     private final static String TAG = "PhoneWindow";
 
     private final static boolean SWEEP_OPEN_MENU = false;
+
+    private final static int DEFAULT_BACKGROUND_FADE_DURATION_MS = 300;
+
     /**
      * Simple callback used by the context menu and its submenus. The options
      * menu submenus do not use this (their behavior is more complex).
@@ -247,6 +250,7 @@
     private Transition mSharedElementExitTransition;
     private Boolean mAllowExitTransitionOverlap;
     private Boolean mAllowEnterTransitionOverlap;
+    private long mBackgroundFadeDurationMillis = -1;
 
     static class WindowManagerHolder {
         static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface(
@@ -3422,6 +3426,12 @@
                             com.android.internal.R.styleable.
                                     Window_windowAllowExitTransitionOverlap, true);
                 }
+                if (mBackgroundFadeDurationMillis < 0) {
+                    mBackgroundFadeDurationMillis = getWindowStyle().getInteger(
+                            com.android.internal.R.styleable.
+                                    Window_windowTransitionBackgroundFadeDuration,
+                            DEFAULT_BACKGROUND_FADE_DURATION_MS);
+                }
             }
         }
     }
@@ -3828,6 +3838,20 @@
         return (mAllowExitTransitionOverlap == null) ? true : mAllowExitTransitionOverlap;
     }
 
+    @Override
+    public long getTransitionBackgroundFadeDuration() {
+        return (mBackgroundFadeDurationMillis < 0) ? DEFAULT_BACKGROUND_FADE_DURATION_MS
+                : mBackgroundFadeDurationMillis;
+    }
+
+    @Override
+    public void setTransitionBackgroundFadeDuration(long fadeDurationMillis) {
+        if (fadeDurationMillis < 0) {
+            throw new IllegalArgumentException("negative durations are not allowed");
+        }
+        mBackgroundFadeDurationMillis = fadeDurationMillis;
+    }
+
     private static final class DrawableFeatureState {
         DrawableFeatureState(int _featureId) {
             featureId = _featureId;
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 684fd9f..cacf66b 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -296,6 +296,7 @@
     WindowState mLastInputMethodWindow = null;
     WindowState mLastInputMethodTargetWindow = null;
 
+    boolean mRecentsVisible;
     int mRecentAppsHeldModifiers;
     boolean mLanguageSwitchKeyPressed;
 
@@ -314,6 +315,9 @@
     int mDemoHdmiRotation;
     boolean mDemoHdmiRotationLock;
 
+    boolean mWakeGestureEnabledSetting;
+    MyWakeGestureListener mWakeGestureListener;
+
     // Default display does not rotate, apps that require non-default orientation will have to
     // have the orientation emulated.
     private boolean mForceDefaultOrientation = false;
@@ -443,6 +447,7 @@
     boolean mSearchKeyShortcutPending;
     boolean mConsumeSearchKeyUp;
     boolean mAssistKeyLongPressed;
+    boolean mPendingMetaAction;
 
     // support for activating the lock screen while the screen is on
     boolean mAllowLockscreenWhenOn;
@@ -573,6 +578,9 @@
             resolver.registerContentObserver(Settings.Secure.getUriFor(
                     Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR), false, this,
                     UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.Secure.getUriFor(
+                    Settings.Secure.WAKE_GESTURE_ENABLED), false, this,
+                    UserHandle.USER_ALL);
             resolver.registerContentObserver(Settings.System.getUriFor(
                     Settings.System.ACCELEROMETER_ROTATION), false, this,
                     UserHandle.USER_ALL);
@@ -603,6 +611,21 @@
         }
     }
 
+    class MyWakeGestureListener extends WakeGestureListener {
+        MyWakeGestureListener(Context context, Handler handler) {
+            super(context, handler);
+        }
+
+        @Override
+        public void onWakeUp() {
+            synchronized (mLock) {
+                if (shouldEnableWakeGestureLp()) {
+                    mPowerManager.wakeUp(SystemClock.uptimeMillis());
+                }
+            }
+        }
+    }
+
     class MyOrientationListener extends WindowOrientationListener {
         MyOrientationListener(Context context, Handler handler) {
             super(context, handler);
@@ -906,6 +929,7 @@
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
 
         mHandler = new PolicyHandler();
+        mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler);
         mOrientationListener = new MyOrientationListener(mContext, mHandler);
         try {
             mOrientationListener.setCurrentRotation(windowManager.getRotation());
@@ -1192,6 +1216,15 @@
                     Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT,
                     UserHandle.USER_CURRENT);
 
+            // Configure wake gesture.
+            boolean wakeGestureEnabledSetting = Settings.Secure.getIntForUser(resolver,
+                    Settings.Secure.WAKE_GESTURE_ENABLED, 0,
+                    UserHandle.USER_CURRENT) != 0;
+            if (mWakeGestureEnabledSetting != wakeGestureEnabledSetting) {
+                mWakeGestureEnabledSetting = wakeGestureEnabledSetting;
+                updateWakeGestureListenerLp();
+            }
+
             // Configure rotation lock.
             int userRotation = Settings.System.getIntForUser(resolver,
                     Settings.System.USER_ROTATION, Surface.ROTATION_0,
@@ -1239,6 +1272,20 @@
         }
     }
 
+    private void updateWakeGestureListenerLp() {
+        if (shouldEnableWakeGestureLp()) {
+            mWakeGestureListener.requestWakeUpTrigger();
+        } else {
+            mWakeGestureListener.cancelWakeUpTrigger();
+        }
+    }
+
+    private boolean shouldEnableWakeGestureLp() {
+        return mWakeGestureEnabledSetting && !mScreenOnEarly
+                && (!mLidControlsSleep || mLidState != LID_CLOSED)
+                && mWakeGestureListener.isSupported();
+    }
+
     private void enablePointerLocation() {
         if (mPointerLocationView == null) {
             mPointerLocationView = new PointerLocationView(mContext);
@@ -2043,6 +2090,12 @@
             }
         }
 
+        // Cancel any pending meta actions if we see any other keys being pressed between the down
+        // of the meta key and its corresponding up.
+        if (mPendingMetaAction && keyCode != KeyEvent.KEYCODE_META_LEFT) {
+            mPendingMetaAction = false;
+        }
+
         // First we always handle the home key here, so applications
         // can never break it, although if keyguard is on, we do let
         // it handle it, because that gives us the correct 5 second
@@ -2237,6 +2290,14 @@
                 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT_OR_SELF);
             }
             return -1;
+        } else if (keyCode == KeyEvent.KEYCODE_META_LEFT) {
+            if (down) {
+                mPendingMetaAction = true;
+            } else if (mPendingMetaAction) {
+                mPendingMetaAction = false;
+                launchAssistAction();
+            }
+            return -1;
         }
 
         // Shortcuts are invoked through Search+key, so intercept those here
@@ -2346,6 +2407,11 @@
             return -1;
         }
 
+        // Reserve all the META modifier combos for system behavior
+        if ((metaState & KeyEvent.META_META_LEFT_ON) != 0) {
+            return -1;
+        }
+
         // Let the application handle the key.
         return 0;
     }
@@ -2567,6 +2633,11 @@
                     }
                 }
             });
+        } else if (mRecentsVisible) {
+            // Recents is started on top of Home, so when we launch home while recents is open, let
+            // it do its own animation and then finish itself
+            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+            hideRecentApps(false);
         } else {
             // no keyguard stuff to worry about, just launch home!
             try {
@@ -2658,6 +2729,7 @@
     public int adjustSystemUiVisibilityLw(int visibility) {
         mStatusBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility);
         mNavigationBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility);
+        mRecentsVisible = (visibility & View.RECENT_APPS_VISIBLE) > 0;
 
         // Reset any bits in mForceClearingStatusBarVisibility that
         // are now clear.
@@ -4461,6 +4533,7 @@
             mKeyguardDelegate.onScreenTurnedOff(why);
         }
         synchronized (mLock) {
+            updateWakeGestureListenerLp();
             updateOrientationListenerLp();
             updateLockScreenTimeout();
         }
@@ -4482,6 +4555,7 @@
 
         synchronized (mLock) {
             mScreenOnEarly = true;
+            updateWakeGestureListenerLp();
             updateOrientationListenerLp();
             updateLockScreenTimeout();
         }
@@ -5056,6 +5130,10 @@
                     PowerManager.GO_TO_SLEEP_REASON_USER,
                     PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
         }
+
+        synchronized (mLock) {
+            updateWakeGestureListenerLp();
+        }
     }
 
     void updateRotation(boolean alwaysSendConfiguration) {
@@ -5516,6 +5594,9 @@
             pw.print(prefix); pw.print("mLastFocusNeedsMenu=");
                     pw.println(mLastFocusNeedsMenu);
         }
+        pw.print(prefix); pw.print("mWakeGestureEnabledSetting=");
+                pw.println(mWakeGestureEnabledSetting);
+
         pw.print(prefix); pw.print("mSupportAutoRotation="); pw.println(mSupportAutoRotation);
         pw.print(prefix); pw.print("mUiMode="); pw.print(mUiMode);
                 pw.print(" mDockMode="); pw.print(mDockMode);
@@ -5657,9 +5738,17 @@
         pw.print(prefix); pw.print("mDemoHdmiRotation="); pw.print(mDemoHdmiRotation);
                 pw.print(" mDemoHdmiRotationLock="); pw.println(mDemoHdmiRotationLock);
         pw.print(prefix); pw.print("mUndockedHdmiRotation="); pw.println(mUndockedHdmiRotation);
+
         mGlobalKeyManager.dump(prefix, pw);
         mStatusBarController.dump(pw, prefix);
         mNavigationBarController.dump(pw, prefix);
         PolicyControl.dump(prefix, pw);
+
+        if (mWakeGestureListener != null) {
+            mWakeGestureListener.dump(pw, prefix);
+        }
+        if (mOrientationListener != null) {
+            mOrientationListener.dump(pw, prefix);
+        }
     }
 }
diff --git a/policy/src/com/android/internal/policy/impl/WakeGestureListener.java b/policy/src/com/android/internal/policy/impl/WakeGestureListener.java
new file mode 100644
index 0000000..9396c2c
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/WakeGestureListener.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl;
+
+import android.os.Handler;
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.TriggerEvent;
+import android.hardware.TriggerEventListener;
+
+import java.io.PrintWriter;
+
+/**
+ * Watches for wake gesture sensor events then invokes the listener.
+ */
+public abstract class WakeGestureListener {
+    private static final String TAG = "WakeGestureListener";
+
+    private final SensorManager mSensorManager;
+    private final Handler mHandler;
+
+    private final Object mLock = new Object();
+
+    private boolean mTriggerRequested;
+    private Sensor mSensor;
+
+    public WakeGestureListener(Context context, Handler handler) {
+        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+        mHandler = handler;
+
+        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_WAKE_GESTURE);
+    }
+
+    public abstract void onWakeUp();
+
+    public boolean isSupported() {
+        synchronized (mLock) {
+            return mSensor != null;
+        }
+    }
+
+    public void requestWakeUpTrigger() {
+        synchronized (mLock) {
+            if (mSensor != null && !mTriggerRequested) {
+                mTriggerRequested = true;
+                mSensorManager.requestTriggerSensor(mListener, mSensor);
+            }
+        }
+    }
+
+    public void cancelWakeUpTrigger() {
+        synchronized (mLock) {
+            if (mSensor != null && mTriggerRequested) {
+                mTriggerRequested = false;
+                mSensorManager.cancelTriggerSensor(mListener, mSensor);
+            }
+        }
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        synchronized (mLock) {
+            pw.println(prefix + TAG);
+            prefix += "  ";
+            pw.println(prefix + "mTriggerRequested=" + mTriggerRequested);
+            pw.println(prefix + "mSensor=" + mSensor);
+        }
+    }
+
+    private final TriggerEventListener mListener = new TriggerEventListener() {
+        @Override
+        public void onTrigger(TriggerEvent event) {
+            synchronized (mLock) {
+                mTriggerRequested = false;
+                mHandler.post(mWakeUpRunnable);
+            }
+        }
+    };
+
+    private final Runnable mWakeUpRunnable = new Runnable() {
+        @Override
+        public void run() {
+            onWakeUp();
+        }
+    };
+}
diff --git a/policy/src/com/android/internal/policy/impl/WindowOrientationListener.java b/policy/src/com/android/internal/policy/impl/WindowOrientationListener.java
index 0c77556..2cc33b5f 100644
--- a/policy/src/com/android/internal/policy/impl/WindowOrientationListener.java
+++ b/policy/src/com/android/internal/policy/impl/WindowOrientationListener.java
@@ -26,6 +26,9 @@
 import android.util.FloatMath;
 import android.util.Log;
 import android.util.Slog;
+import android.util.TimeUtils;
+
+import java.io.PrintWriter;
 
 /**
  * A special helper class used by the WindowManager
@@ -181,6 +184,19 @@
      */
     public abstract void onProposedRotationChanged(int rotation);
 
+    public void dump(PrintWriter pw, String prefix) {
+        synchronized (mLock) {
+            pw.println(prefix + TAG);
+            prefix += "  ";
+            pw.println(prefix + "mEnabled=" + mEnabled);
+            pw.println(prefix + "mCurrentRotation=" + mCurrentRotation);
+            pw.println(prefix + "mSensor=" + mSensor);
+            pw.println(prefix + "mRate=" + mRate);
+
+            mSensorEventListener.dumpLocked(pw, prefix);
+        }
+    }
+
     /**
      * This class filters the raw accelerometer data and tries to detect actual changes in
      * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters,
@@ -342,6 +358,14 @@
             /* ROTATION_270 */ { -25, 65 }
         };
 
+        // The tilt angle below which we conclude that the user is holding the device
+        // overhead reading in bed and lock into that state.
+        private final int TILT_OVERHEAD_ENTER = -40;
+
+        // The tilt angle above which we conclude that the user would like a rotation
+        // change to occur and unlock from the overhead state.
+        private final int TILT_OVERHEAD_EXIT = -15;
+
         // The gap angle in degrees between adjacent orientation angles for hysteresis.
         // This creates a "dead zone" between the current orientation and a proposed
         // adjacent orientation.  No orientation proposal is made when the orientation
@@ -364,12 +388,18 @@
 
         // Timestamp when the device last appeared to be flat for sure (the flat delay elapsed).
         private long mFlatTimestampNanos;
+        private boolean mFlat;
 
         // Timestamp when the device last appeared to be swinging.
         private long mSwingTimestampNanos;
+        private boolean mSwinging;
 
         // Timestamp when the device last appeared to be undergoing external acceleration.
         private long mAccelerationTimestampNanos;
+        private boolean mAccelerating;
+
+        // Whether we are locked into an overhead usage mode.
+        private boolean mOverhead;
 
         // History of observed tilt angles.
         private static final int TILT_HISTORY_SIZE = 40;
@@ -381,6 +411,19 @@
             return mProposedRotation;
         }
 
+        public void dumpLocked(PrintWriter pw, String prefix) {
+            pw.println(prefix + "mProposedRotation=" + mProposedRotation);
+            pw.println(prefix + "mPredictedRotation=" + mPredictedRotation);
+            pw.println(prefix + "mLastFilteredX=" + mLastFilteredX);
+            pw.println(prefix + "mLastFilteredY=" + mLastFilteredY);
+            pw.println(prefix + "mLastFilteredZ=" + mLastFilteredZ);
+            pw.println(prefix + "mTiltHistory={last: " + getLastTiltLocked() + "}");
+            pw.println(prefix + "mFlat=" + mFlat);
+            pw.println(prefix + "mSwinging=" + mSwinging);
+            pw.println(prefix + "mAccelerating=" + mAccelerating);
+            pw.println(prefix + "mOverhead=" + mOverhead);
+        }
+
         @Override
         public void onAccuracyChanged(Sensor sensor, int accuracy) {
         }
@@ -478,7 +521,18 @@
 
                         // If the tilt angle is too close to horizontal then we cannot determine
                         // the orientation angle of the screen.
-                        if (Math.abs(tiltAngle) > MAX_TILT) {
+                        if (tiltAngle <= TILT_OVERHEAD_ENTER) {
+                            mOverhead = true;
+                        } else if (tiltAngle >= TILT_OVERHEAD_EXIT) {
+                            mOverhead = false;
+                        }
+                        if (mOverhead) {
+                            if (LOG) {
+                                Slog.v(TAG, "Ignoring sensor data, device is overhead: "
+                                        + "tiltAngle=" + tiltAngle);
+                            }
+                            clearPredictedRotationLocked();
+                        } else if (Math.abs(tiltAngle) > MAX_TILT) {
                             if (LOG) {
                                 Slog.v(TAG, "Ignoring sensor data, tilt angle too high: "
                                         + "tiltAngle=" + tiltAngle);
@@ -526,6 +580,9 @@
                         }
                     }
                 }
+                mFlat = isFlat;
+                mSwinging = isSwinging;
+                mAccelerating = isAccelerating;
 
                 // Determine new proposed rotation.
                 oldProposedRotation = mProposedRotation;
@@ -543,6 +600,7 @@
                             + ", isAccelerating=" + isAccelerating
                             + ", isFlat=" + isFlat
                             + ", isSwinging=" + isSwinging
+                            + ", isOverhead=" + mOverhead
                             + ", timeUntilSettledMS=" + remainingMS(now,
                                     mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS)
                             + ", timeUntilAccelerationDelayExpiredMS=" + remainingMS(now,
@@ -660,8 +718,12 @@
             mLastFilteredTimestampNanos = Long.MIN_VALUE;
             mProposedRotation = -1;
             mFlatTimestampNanos = Long.MIN_VALUE;
+            mFlat = false;
             mSwingTimestampNanos = Long.MIN_VALUE;
+            mSwinging = false;
             mAccelerationTimestampNanos = Long.MIN_VALUE;
+            mAccelerating = false;
+            mOverhead = false;
             clearPredictedRotationLocked();
             clearTiltHistoryLocked();
         }
@@ -726,6 +788,11 @@
             return mTiltHistoryTimestampNanos[index] != Long.MIN_VALUE ? index : -1;
         }
 
+        private float getLastTiltLocked() {
+            int index = nextTiltHistoryIndexLocked(mTiltHistoryIndex);
+            return index >= 0 ? mTiltHistory[index] : Float.NaN;
+        }
+
         private float remainingMS(long now, long until) {
             return now >= until ? 0 : (until - now) * 0.000001f;
         }
diff --git a/rs/java/android/renderscript/FieldPacker.java b/rs/java/android/renderscript/FieldPacker.java
index 723ab24..0e57232 100644
--- a/rs/java/android/renderscript/FieldPacker.java
+++ b/rs/java/android/renderscript/FieldPacker.java
@@ -231,10 +231,18 @@
 
     public void addObj(BaseObj obj) {
         if (obj != null) {
-            // FIXME: this is fine for 32-bit but needs a path for 64-bit
-            addI32((int)obj.getID(null));
+            if (RenderScript.sPointerSize == 8) {
+                addI64(obj.getID(null));
+            }
+            else {
+                addI32((int)obj.getID(null));
+            }
         } else {
-            addI32(0);
+            if (RenderScript.sPointerSize == 8) {
+                addI64(0);
+            } else {
+                addI32(0);
+            }
         }
     }
 
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index c748c1b..44de480 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -82,6 +82,12 @@
     */
     public static final int CREATE_FLAG_LOW_POWER = 0x0004;
 
+    /*
+     * Detect the bitness of the VM to allow FieldPacker to do the right thing.
+     */
+    static native int rsnSystemGetPointerSize();
+    static int sPointerSize;
+
     static {
         sInitialized = false;
         if (!SystemProperties.getBoolean("config.disable_renderscript", false)) {
@@ -99,6 +105,7 @@
                 System.loadLibrary("rs_jni");
                 _nInit();
                 sInitialized = true;
+                sPointerSize = rsnSystemGetPointerSize();
             } catch (UnsatisfiedLinkError e) {
                 Log.e(LOG_TAG, "Error loading RS jni library: " + e);
                 throw new RSRuntimeException("Error loading RS jni library: " + e);
diff --git a/rs/java/android/renderscript/ScriptC.java b/rs/java/android/renderscript/ScriptC.java
index 9e76f52..3176e28 100644
--- a/rs/java/android/renderscript/ScriptC.java
+++ b/rs/java/android/renderscript/ScriptC.java
@@ -67,6 +67,26 @@
     }
 
     /**
+     * Only intended for use by the generated derived classes.
+     *
+     * @param rs
+     * @hide
+     */
+    protected ScriptC(RenderScript rs, String resName, byte[] bitcode32, byte[] bitcode64) {
+        super(0, rs);
+        long id = 0;
+        if (RenderScript.sPointerSize == 4) {
+            id = internalStringCreate(rs, resName, bitcode32);
+        } else {
+            id = internalStringCreate(rs, resName, bitcode64);
+        }
+        if (id == 0) {
+            throw new RSRuntimeException("Loading of ScriptC script failed.");
+        }
+        setID(id);
+    }
+
+    /**
      * Name of the file that holds the object cache.
      */
     private static final String CACHE_PATH = "com.android.renderscript.cache";
@@ -113,4 +133,17 @@
         //        Log.v(TAG, "Create script for resource = " + resName);
         return rs.nScriptCCreate(resName, mCachePath, pgm, pgmLength);
     }
+
+    private static synchronized long internalStringCreate(RenderScript rs, String resName, byte[] bitcode) {
+        // Create the RS cache path if we haven't done so already.
+        if (mCachePath == null) {
+            File f = new File(rs.mCacheDir, CACHE_PATH);
+            mCachePath = f.getAbsolutePath();
+            f.mkdirs();
+        }
+        //        Log.v(TAG, "Create script for resource = " + resName);
+        return rs.nScriptCCreate(resName, mCachePath, bitcode, bitcode.length);
+    }
+
+
 }
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 18a2e31..ae39b05 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -1573,6 +1573,12 @@
     free(prims);
 }
 
+static jint
+nSystemGetPointerSize(JNIEnv *_env, jobject _this) {
+    return (jint)sizeof(void*);
+}
+
+
 // ---------------------------------------------------------------------------
 
 
@@ -1708,6 +1714,7 @@
 {"rsnMeshGetVertices",               "(JJ[JI)V",                              (void*)nMeshGetVertices },
 {"rsnMeshGetIndices",                "(JJ[J[II)V",                            (void*)nMeshGetIndices },
 
+{"rsnSystemGetPointerSize",          "()I",                                   (void*)nSystemGetPointerSize },
 };
 
 static int registerFuncs(JNIEnv *_env)
diff --git a/services/Android.mk b/services/Android.mk
index 5fcef64..b4de903 100644
--- a/services/Android.mk
+++ b/services/Android.mk
@@ -25,6 +25,7 @@
     backup \
     devicepolicy \
     print \
+    restrictions \
     usb \
     voiceinteraction
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index eca1bc1..1be1572 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1640,6 +1640,17 @@
                 pw.println("}]");
                 pw.println();
             }
+            final int windowCount = mSecurityPolicy.mWindows.size();
+            for (int j = 0; j < windowCount; j++) {
+                if (j > 0) {
+                    pw.append(',');
+                    pw.println();
+                }
+                pw.append("Window[");
+                AccessibilityWindowInfo window = mSecurityPolicy.mWindows.get(j);
+                pw.append(window.toString());
+                pw.append(']');
+            }
         }
     }
 
@@ -3283,7 +3294,7 @@
                 }
 
                 if (mTouchInteractionInProgress && activeWindowGone) {
-                    mActiveWindowId = INVALID_WINDOW_ID;
+                    mActiveWindowId = mFocusedWindowId;
                 }
 
                 // Focused window may change the active one, so set the
@@ -3291,7 +3302,7 @@
                 for (int i = 0; i < windowCount; i++) {
                     AccessibilityWindowInfo window = mWindows.get(i);
                     if (window.getId() == mActiveWindowId) {
-                       window.setActive(true);
+                        window.setActive(true);
                     }
                 }
             }
@@ -3336,10 +3347,11 @@
                     // The active window also determined events from which
                     // windows are delivered.
                     synchronized (mLock) {
-                        mFocusedWindowId = getFocusedWindowId();
-                        if (mWindowsForAccessibilityCallback == null
-                                && windowId == mFocusedWindowId) {
-                            mActiveWindowId = windowId;
+                        if (mWindowsForAccessibilityCallback == null) {
+                            mFocusedWindowId = getFocusedWindowId();
+                            if (windowId == mFocusedWindowId) {
+                                mActiveWindowId = windowId;
+                            }
                         }
                     }
                 } break;
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 87b1d32..7a67d63 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -843,6 +843,15 @@
                 throw new IllegalArgumentException("Unknown component " + componentName);
             }
 
+            // Ensure that the service specified by the passed intent belongs to the same package
+            // as provides the passed widget id.
+            String widgetIdPackage = id.provider.info.provider.getPackageName();
+            String servicePackage = componentName.getPackageName();
+            if (!servicePackage.equals(widgetIdPackage)) {
+                throw new SecurityException("Specified intent doesn't belong to the same package"
+                        + " as the provided AppWidget id");
+            }
+
             // If there is already a connection made for this service intent, then disconnect from
             // that first. (This does not allow multiple connections to the same service under
             // the same key)
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 0082b1e..14c15a7 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -26,6 +26,7 @@
 import android.app.backup.BackupAgent;
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupTransport;
 import android.app.backup.FullBackup;
 import android.app.backup.RestoreSet;
 import android.app.backup.IBackupManager;
@@ -82,7 +83,6 @@
 import android.util.SparseArray;
 import android.util.StringBuilderPrinter;
 
-import com.android.internal.backup.BackupConstants;
 import com.android.internal.backup.IBackupTransport;
 import com.android.internal.backup.IObbBackupService;
 import com.android.server.AppWidgetBackupBridge;
@@ -2098,7 +2098,7 @@
             }
 
             mAgentBinder = null;
-            mStatus = BackupConstants.TRANSPORT_OK;
+            mStatus = BackupTransport.TRANSPORT_OK;
 
             // Sanity check: if the queue is empty we have no work to do.
             if (mOriginalQueue.isEmpty()) {
@@ -2121,14 +2121,14 @@
                 EventLog.writeEvent(EventLogTags.BACKUP_START, transportName);
 
                 // If we haven't stored package manager metadata yet, we must init the transport.
-                if (mStatus == BackupConstants.TRANSPORT_OK && pmState.length() <= 0) {
+                if (mStatus == BackupTransport.TRANSPORT_OK && pmState.length() <= 0) {
                     Slog.i(TAG, "Initializing (wiping) backup state and transport storage");
                     addBackupTrace("initializing transport " + transportName);
                     resetBackupState(mStateDir);  // Just to make sure.
                     mStatus = mTransport.initializeDevice();
 
                     addBackupTrace("transport.initializeDevice() == " + mStatus);
-                    if (mStatus == BackupConstants.TRANSPORT_OK) {
+                    if (mStatus == BackupTransport.TRANSPORT_OK) {
                         EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
                     } else {
                         EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(initialize)");
@@ -2141,7 +2141,7 @@
                 // directly and use a synthetic BackupRequest.  We always run this pass
                 // because it's cheap and this way we guarantee that we don't get out of
                 // step even if we're selecting among various transports at run time.
-                if (mStatus == BackupConstants.TRANSPORT_OK) {
+                if (mStatus == BackupTransport.TRANSPORT_OK) {
                     PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
                             mPackageManager, allAgentPackages());
                     mStatus = invokeAgentForBackup(PACKAGE_MANAGER_SENTINEL,
@@ -2149,7 +2149,7 @@
                     addBackupTrace("PMBA invoke: " + mStatus);
                 }
 
-                if (mStatus == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
+                if (mStatus == BackupTransport.TRANSPORT_NOT_INITIALIZED) {
                     // The backend reports that our dataset has been wiped.  Note this in
                     // the event log; the no-success code below will reset the backup
                     // state as well.
@@ -2158,13 +2158,13 @@
             } catch (Exception e) {
                 Slog.e(TAG, "Error in backup thread", e);
                 addBackupTrace("Exception in backup thread: " + e);
-                mStatus = BackupConstants.TRANSPORT_ERROR;
+                mStatus = BackupTransport.TRANSPORT_ERROR;
             } finally {
                 // If we've succeeded so far, invokeAgentForBackup() will have run the PM
                 // metadata and its completion/timeout callback will continue the state
                 // machine chain.  If it failed that won't happen; we handle that now.
                 addBackupTrace("exiting prelim: " + mStatus);
-                if (mStatus != BackupConstants.TRANSPORT_OK) {
+                if (mStatus != BackupTransport.TRANSPORT_OK) {
                     // if things went wrong at this point, we need to
                     // restage everything and try again later.
                     resetBackupState(mStateDir);  // Just to make sure.
@@ -2176,7 +2176,7 @@
         // Transport has been initialized and the PM metadata submitted successfully
         // if that was warranted.  Now we process the single next thing in the queue.
         void invokeNextAgent() {
-            mStatus = BackupConstants.TRANSPORT_OK;
+            mStatus = BackupTransport.TRANSPORT_OK;
             addBackupTrace("invoke q=" + mQueue.size());
 
             // Sanity check that we have work to do.  If not, skip to the end where
@@ -2236,39 +2236,39 @@
                         // done here as long as we're successful so far.
                     } else {
                         // Timeout waiting for the agent
-                        mStatus = BackupConstants.AGENT_ERROR;
+                        mStatus = BackupTransport.AGENT_ERROR;
                     }
                 } catch (SecurityException ex) {
                     // Try for the next one.
                     Slog.d(TAG, "error in bind/backup", ex);
-                    mStatus = BackupConstants.AGENT_ERROR;
+                    mStatus = BackupTransport.AGENT_ERROR;
                             addBackupTrace("agent SE");
                 }
             } catch (NameNotFoundException e) {
                 Slog.d(TAG, "Package does not exist; skipping");
                 addBackupTrace("no such package");
-                mStatus = BackupConstants.AGENT_UNKNOWN;
+                mStatus = BackupTransport.AGENT_UNKNOWN;
             } finally {
                 mWakelock.setWorkSource(null);
 
                 // If there was an agent error, no timeout/completion handling will occur.
                 // That means we need to direct to the next state ourselves.
-                if (mStatus != BackupConstants.TRANSPORT_OK) {
+                if (mStatus != BackupTransport.TRANSPORT_OK) {
                     BackupState nextState = BackupState.RUNNING_QUEUE;
                     mAgentBinder = null;
 
                     // An agent-level failure means we reenqueue this one agent for
                     // a later retry, but otherwise proceed normally.
-                    if (mStatus == BackupConstants.AGENT_ERROR) {
+                    if (mStatus == BackupTransport.AGENT_ERROR) {
                         if (MORE_DEBUG) Slog.i(TAG, "Agent failure for " + request.packageName
                                 + " - restaging");
                         dataChangedImpl(request.packageName);
-                        mStatus = BackupConstants.TRANSPORT_OK;
+                        mStatus = BackupTransport.TRANSPORT_OK;
                         if (mQueue.isEmpty()) nextState = BackupState.FINAL;
-                    } else if (mStatus == BackupConstants.AGENT_UNKNOWN) {
+                    } else if (mStatus == BackupTransport.AGENT_UNKNOWN) {
                         // Failed lookup of the app, so we couldn't bring up an agent, but
                         // we're otherwise fine.  Just drop it and go on to the next as usual.
-                        mStatus = BackupConstants.TRANSPORT_OK;
+                        mStatus = BackupTransport.TRANSPORT_OK;
                     } else {
                         // Transport-level failure means we reenqueue everything
                         revertAndEndBackup();
@@ -2297,7 +2297,7 @@
             // If everything actually went through and this is the first time we've
             // done a backup, we can now record what the current backup dataset token
             // is.
-            if ((mCurrentToken == 0) && (mStatus == BackupConstants.TRANSPORT_OK)) {
+            if ((mCurrentToken == 0) && (mStatus == BackupTransport.TRANSPORT_OK)) {
                 addBackupTrace("success; recording token");
                 try {
                     mCurrentToken = mTransport.getCurrentRestoreSet();
@@ -2314,7 +2314,7 @@
             // state machine sequence and the wakelock is refcounted.
             synchronized (mQueueLock) {
                 mBackupRunning = false;
-                if (mStatus == BackupConstants.TRANSPORT_NOT_INITIALIZED) {
+                if (mStatus == BackupTransport.TRANSPORT_NOT_INITIALIZED) {
                     // Make sure we back up everything and perform the one-time init
                     clearMetadata();
                     if (DEBUG) Slog.d(TAG, "Server requires init; rerunning");
@@ -2395,7 +2395,7 @@
                 EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName,
                         e.toString());
                 agentErrorCleanup();
-                return BackupConstants.AGENT_ERROR;
+                return BackupTransport.AGENT_ERROR;
             }
 
             // At this point the agent is off and running.  The next thing to happen will
@@ -2403,7 +2403,7 @@
             // for transport, or a timeout.  Either way the next phase will happen in
             // response to the TimeoutHandler interface callbacks.
             addBackupTrace("invoke success");
-            return BackupConstants.TRANSPORT_OK;
+            return BackupTransport.TRANSPORT_OK;
         }
 
         public void failAgent(IBackupAgent agent, String message) {
@@ -2484,11 +2484,11 @@
             addBackupTrace("operation complete");
 
             ParcelFileDescriptor backupData = null;
-            mStatus = BackupConstants.TRANSPORT_OK;
+            mStatus = BackupTransport.TRANSPORT_OK;
             try {
                 int size = (int) mBackupDataName.length();
                 if (size > 0) {
-                    if (mStatus == BackupConstants.TRANSPORT_OK) {
+                    if (mStatus == BackupTransport.TRANSPORT_OK) {
                         backupData = ParcelFileDescriptor.open(mBackupDataName,
                                 ParcelFileDescriptor.MODE_READ_ONLY);
                         addBackupTrace("sending data to transport");
@@ -2501,7 +2501,7 @@
                     // renaming *all* the output state files (see below) until that happens.
 
                     addBackupTrace("data delivered: " + mStatus);
-                    if (mStatus == BackupConstants.TRANSPORT_OK) {
+                    if (mStatus == BackupTransport.TRANSPORT_OK) {
                         addBackupTrace("finishing op on transport");
                         mStatus = mTransport.finishBackup();
                         addBackupTrace("finished: " + mStatus);
@@ -2514,7 +2514,7 @@
                 // After successful transport, delete the now-stale data
                 // and juggle the files so that next time we supply the agent
                 // with the new state file it just created.
-                if (mStatus == BackupConstants.TRANSPORT_OK) {
+                if (mStatus == BackupTransport.TRANSPORT_OK) {
                     mBackupDataName.delete();
                     mNewStateName.renameTo(mSavedStateName);
                     EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, pkgName, size);
@@ -2525,7 +2525,7 @@
             } catch (Exception e) {
                 Slog.e(TAG, "Transport error backing up " + pkgName, e);
                 EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
-                mStatus = BackupConstants.TRANSPORT_ERROR;
+                mStatus = BackupTransport.TRANSPORT_ERROR;
             } finally {
                 try { if (backupData != null) backupData.close(); } catch (IOException e) {}
             }
@@ -2533,7 +2533,7 @@
             // If we encountered an error here it's a transport-level failure.  That
             // means we need to halt everything and reschedule everything for next time.
             final BackupState nextState;
-            if (mStatus != BackupConstants.TRANSPORT_OK) {
+            if (mStatus != BackupTransport.TRANSPORT_OK) {
                 revertAndEndBackup();
                 nextState = BackupState.FINAL;
             } else {
@@ -4847,7 +4847,7 @@
             mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT);
 
             // Assume error until we successfully init everything
-            mStatus = BackupConstants.TRANSPORT_ERROR;
+            mStatus = BackupTransport.TRANSPORT_ERROR;
 
             try {
                 // TODO: Log this before getAvailableRestoreSets, somehow
@@ -4902,7 +4902,7 @@
                 return;
             }
 
-            mStatus = BackupConstants.TRANSPORT_OK;
+            mStatus = BackupTransport.TRANSPORT_OK;
             executeNextState(RestoreState.DOWNLOAD_DATA);
         }
 
@@ -4917,7 +4917,7 @@
             try {
                 mStatus = mTransport.startRestore(mToken,
                         mRestorePackages.toArray(new PackageInfo[0]));
-                if (mStatus != BackupConstants.TRANSPORT_OK) {
+                if (mStatus != BackupTransport.TRANSPORT_OK) {
                     Slog.e(TAG, "Error starting restore operation");
                     EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
                     executeNextState(RestoreState.FINAL);
@@ -4926,7 +4926,7 @@
             } catch (RemoteException e) {
                 Slog.e(TAG, "Error communicating with transport for restore");
                 EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
-                mStatus = BackupConstants.TRANSPORT_ERROR;
+                mStatus = BackupTransport.TRANSPORT_ERROR;
                 executeNextState(RestoreState.FINAL);
                 return;
             }
@@ -4941,14 +4941,14 @@
                 if (packageName == null) {
                     Slog.e(TAG, "Error getting first restore package");
                     EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
-                    mStatus = BackupConstants.TRANSPORT_ERROR;
+                    mStatus = BackupTransport.TRANSPORT_ERROR;
                     executeNextState(RestoreState.FINAL);
                     return;
                 } else if (packageName.equals("")) {
                     Slog.i(TAG, "No restore data available");
                     int millis = (int) (SystemClock.elapsedRealtime() - mStartRealtime);
                     EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, 0, millis);
-                    mStatus = BackupConstants.TRANSPORT_OK;
+                    mStatus = BackupTransport.TRANSPORT_OK;
                     executeNextState(RestoreState.FINAL);
                     return;
                 } else if (!packageName.equals(PACKAGE_MANAGER_SENTINEL)) {
@@ -4979,7 +4979,7 @@
                     Slog.e(TAG, "No restore metadata available, so not restoring settings");
                     EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, PACKAGE_MANAGER_SENTINEL,
                     "Package manager restore metadata missing");
-                    mStatus = BackupConstants.TRANSPORT_ERROR;
+                    mStatus = BackupTransport.TRANSPORT_ERROR;
                     mBackupHandler.removeMessages(MSG_BACKUP_RESTORE_STEP, this);
                     executeNextState(RestoreState.FINAL);
                     return;
@@ -4987,7 +4987,7 @@
             } catch (RemoteException e) {
                 Slog.e(TAG, "Error communicating with transport for restore");
                 EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
-                mStatus = BackupConstants.TRANSPORT_ERROR;
+                mStatus = BackupTransport.TRANSPORT_ERROR;
                 mBackupHandler.removeMessages(MSG_BACKUP_RESTORE_STEP, this);
                 executeNextState(RestoreState.FINAL);
                 return;
@@ -5118,7 +5118,7 @@
                 }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Unable to fetch restore data from transport");
-                mStatus = BackupConstants.TRANSPORT_ERROR;
+                mStatus = BackupTransport.TRANSPORT_ERROR;
                 executeNextState(RestoreState.FINAL);
             }
         }
@@ -5206,7 +5206,7 @@
                     Slog.e(TAG, "SElinux restorecon failed for " + downloadFile);
                 }
 
-                if (mTransport.getRestoreData(stage) != BackupConstants.TRANSPORT_OK) {
+                if (mTransport.getRestoreData(stage) != BackupTransport.TRANSPORT_OK) {
                     // Transport-level failure, so we wind everything up and
                     // terminate the restore operation.
                     Slog.e(TAG, "Error getting restore data for " + packageName);
@@ -5450,12 +5450,12 @@
                     long startRealtime = SystemClock.elapsedRealtime();
                     int status = transport.initializeDevice();
 
-                    if (status == BackupConstants.TRANSPORT_OK) {
+                    if (status == BackupTransport.TRANSPORT_OK) {
                         status = transport.finishBackup();
                     }
 
                     // Okay, the wipe really happened.  Clean up our local bookkeeping.
-                    if (status == BackupConstants.TRANSPORT_OK) {
+                    if (status == BackupTransport.TRANSPORT_OK) {
                         Slog.i(TAG, "Device init successful");
                         int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
                         EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index f66f7ac..657d5ec 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4438,7 +4438,9 @@
                                 mIsProvisioningNetwork.set(true);
                                 MobileDataStateTracker mdst = (MobileDataStateTracker)
                                         mNetTrackers[ConnectivityManager.TYPE_MOBILE];
-                                mdst.setInternalDataEnable(false);
+
+                                // Disable radio until user starts provisioning
+                                mdst.setRadio(false);
                             } else {
                                 if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), no url");
                             }
@@ -4950,17 +4952,24 @@
         // Mark notification as not visible
         setProvNotificationVisible(false, ConnectivityManager.TYPE_MOBILE_HIPRI, null, null);
 
-        // If provisioning network handle as a special case,
+        // Check airplane mode
+        boolean isAirplaneModeOn = Settings.System.getInt(mContext.getContentResolver(),
+                Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+        // If provisioning network and not in airplane mode handle as a special case,
         // otherwise launch browser with the intent directly.
-        if (mIsProvisioningNetwork.get()) {
+        if (mIsProvisioningNetwork.get() && !isAirplaneModeOn) {
             if (DBG) log("handleMobileProvisioningAction: on prov network enable then launch");
+            mIsProvisioningNetwork.set(false);
 //            mIsStartingProvisioning.set(true);
 //            MobileDataStateTracker mdst = (MobileDataStateTracker)
 //                    mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+            // Radio was disabled on CMP_RESULT_CODE_PROVISIONING_NETWORK, enable it here
+//            mdst.setRadio(true);
 //            mdst.setEnableFailFastMobileData(DctConstants.ENABLED);
 //            mdst.enableMobileProvisioning(url);
         } else {
             if (DBG) log("handleMobileProvisioningAction: not prov network");
+            mIsProvisioningNetwork.set(false);
             // Check for  apps that can handle provisioning first
             Intent provisioningIntent = new Intent(TelephonyIntents.ACTION_CARRIER_SETUP);
             provisioningIntent.addCategory(TelephonyIntents.CATEGORY_MCCMNC_PREFIX
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 4a8bf72..af38664 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -19,10 +19,12 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.Message;
 import android.os.PowerManager;
@@ -33,62 +35,62 @@
 import android.util.Log;
 import android.util.Slog;
 
+import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.FileReader;
+import java.io.PrintWriter;
 
 /**
- * <p>DockObserver monitors for a docking station.
+ * DockObserver monitors for a docking station.
  */
-final class DockObserver extends UEventObserver {
-    private static final String TAG = DockObserver.class.getSimpleName();
+final class DockObserver extends SystemService {
+    private static final String TAG = "DockObserver";
 
     private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
     private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
 
     private static final int MSG_DOCK_STATE_CHANGED = 0;
 
-    private final Object mLock = new Object();
-
-    private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
-    private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
-
-    private boolean mSystemReady;
-
-    private final Context mContext;
     private final PowerManager mPowerManager;
     private final PowerManager.WakeLock mWakeLock;
 
-    public DockObserver(Context context) {
-        mContext = context;
+    private final Object mLock = new Object();
 
-        mPowerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+    private boolean mSystemReady;
+
+    private int mActualDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+
+    private int mReportedDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+    private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+
+    private boolean mUpdatesStopped;
+
+    public DockObserver(Context context) {
+        super(context);
+
+        mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
 
         init();  // set initial status
-        startObserving(DOCK_UEVENT_MATCH);
+
+        mObserver.startObserving(DOCK_UEVENT_MATCH);
     }
 
     @Override
-    public void onUEvent(UEventObserver.UEvent event) {
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Slog.v(TAG, "Dock UEVENT: " + event.toString());
-        }
+    public void onStart() {
+        publishBinderService(TAG, new BinderService());
+    }
 
-        synchronized (mLock) {
-            try {
-                int newState = Integer.parseInt(event.get("SWITCH_STATE"));
-                if (newState != mDockState) {
-                    mPreviousDockState = mDockState;
-                    mDockState = newState;
-                    if (mSystemReady) {
-                        // Wake up immediately when docked or undocked.
-                        mPowerManager.wakeUp(SystemClock.uptimeMillis());
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == PHASE_ACTIVITY_MANAGER_READY) {
+            synchronized (mLock) {
+                mSystemReady = true;
 
-                        updateLocked();
-                    }
+                // don't bother broadcasting undocked here
+                if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+                    updateLocked();
                 }
-            } catch (NumberFormatException e) {
-                Slog.e(TAG, "Could not parse switch state from event " + event);
             }
         }
     }
@@ -100,8 +102,8 @@
                 FileReader file = new FileReader(DOCK_STATE_PATH);
                 try {
                     int len = file.read(buffer, 0, 1024);
-                    mDockState = Integer.valueOf((new String(buffer, 0, len)).trim());
-                    mPreviousDockState = mDockState;
+                    setActualDockStateLocked(Integer.valueOf((new String(buffer, 0, len)).trim()));
+                    mPreviousDockState = mActualDockState;
                 } finally {
                     file.close();
                 }
@@ -113,13 +115,21 @@
         }
     }
 
-    void systemReady() {
-        synchronized (mLock) {
-            // don't bother broadcasting undocked here
-            if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+    private void setActualDockStateLocked(int newState) {
+        mActualDockState = newState;
+        if (!mUpdatesStopped) {
+            setDockStateLocked(newState);
+        }
+    }
+
+    private void setDockStateLocked(int newState) {
+        if (newState != mReportedDockState) {
+            mReportedDockState = newState;
+            if (mSystemReady) {
+                // Wake up immediately when docked or undocked.
+                mPowerManager.wakeUp(SystemClock.uptimeMillis());
                 updateLocked();
             }
-            mSystemReady = true;
         }
     }
 
@@ -130,10 +140,13 @@
 
     private void handleDockStateChange() {
         synchronized (mLock) {
-            Slog.i(TAG, "Dock state changed: " + mDockState);
+            Slog.i(TAG, "Dock state changed from " + mPreviousDockState + " to "
+                    + mReportedDockState);
+            final int previousDockState = mPreviousDockState;
+            mPreviousDockState = mReportedDockState;
 
             // Skip the dock intent if not yet provisioned.
-            final ContentResolver cr = mContext.getContentResolver();
+            final ContentResolver cr = getContext().getContentResolver();
             if (Settings.Global.getInt(cr,
                     Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
                 Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
@@ -143,27 +156,27 @@
             // Pack up the values and broadcast them to everyone
             Intent intent = new Intent(Intent.ACTION_DOCK_EVENT);
             intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-            intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState);
+            intent.putExtra(Intent.EXTRA_DOCK_STATE, mReportedDockState);
 
             // Play a sound to provide feedback to confirm dock connection.
             // Particularly useful for flaky contact pins...
             if (Settings.Global.getInt(cr,
                     Settings.Global.DOCK_SOUNDS_ENABLED, 1) == 1) {
                 String whichSound = null;
-                if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
-                    if ((mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
-                        (mPreviousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
-                        (mPreviousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) {
+                if (mReportedDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+                    if ((previousDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
+                        (previousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
+                        (previousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) {
                         whichSound = Settings.Global.DESK_UNDOCK_SOUND;
-                    } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) {
+                    } else if (previousDockState == Intent.EXTRA_DOCK_STATE_CAR) {
                         whichSound = Settings.Global.CAR_UNDOCK_SOUND;
                     }
                 } else {
-                    if ((mDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
-                        (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
-                        (mDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) {
+                    if ((mReportedDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
+                        (mReportedDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
+                        (mReportedDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) {
                         whichSound = Settings.Global.DESK_DOCK_SOUND;
-                    } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
+                    } else if (mReportedDockState == Intent.EXTRA_DOCK_STATE_CAR) {
                         whichSound = Settings.Global.CAR_DOCK_SOUND;
                     }
                 }
@@ -173,7 +186,8 @@
                     if (soundPath != null) {
                         final Uri soundUri = Uri.parse("file://" + soundPath);
                         if (soundUri != null) {
-                            final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
+                            final Ringtone sfx = RingtoneManager.getRingtone(
+                                    getContext(), soundUri);
                             if (sfx != null) {
                                 sfx.setStreamType(AudioManager.STREAM_SYSTEM);
                                 sfx.play();
@@ -186,7 +200,7 @@
             // Send the dock event intent.
             // There are many components in the system watching for this so as to
             // adjust audio routing, screen orientation, etc.
-            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+            getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
 
             // Release the wake lock that was acquired when the message was posted.
             mWakeLock.release();
@@ -203,4 +217,71 @@
             }
         }
     };
+
+    private final UEventObserver mObserver = new UEventObserver() {
+        @Override
+        public void onUEvent(UEventObserver.UEvent event) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Slog.v(TAG, "Dock UEVENT: " + event.toString());
+            }
+
+            try {
+                synchronized (mLock) {
+                    setActualDockStateLocked(Integer.parseInt(event.get("SWITCH_STATE")));
+                }
+            } catch (NumberFormatException e) {
+                Slog.e(TAG, "Could not parse switch state from event " + event);
+            }
+        }
+    };
+
+    private final class BinderService extends Binder {
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                    != PackageManager.PERMISSION_GRANTED) {
+                pw.println("Permission Denial: can't dump dock observer service from from pid="
+                        + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingUid());
+                return;
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    if (args == null || args.length == 0 || "-a".equals(args[0])) {
+                        pw.println("Current Dock Observer Service state:");
+                        if (mUpdatesStopped) {
+                            pw.println("  (UPDATES STOPPED -- use 'reset' to restart)");
+                        }
+                        pw.println("  reported state: " + mReportedDockState);
+                        pw.println("  previous state: " + mPreviousDockState);
+                        pw.println("  actual state: " + mActualDockState);
+                    } else if (args.length == 3 && "set".equals(args[0])) {
+                        String key = args[1];
+                        String value = args[2];
+                        try {
+                            if ("state".equals(key)) {
+                                mUpdatesStopped = true;
+                                setDockStateLocked(Integer.parseInt(value));
+                            } else {
+                                pw.println("Unknown set option: " + key);
+                            }
+                        } catch (NumberFormatException ex) {
+                            pw.println("Bad value: " + value);
+                        }
+                    } else if (args.length == 1 && "reset".equals(args[0])) {
+                        mUpdatesStopped = false;
+                        setDockStateLocked(mActualDockState);
+                    } else {
+                        pw.println("Dump current dock state, or:");
+                        pw.println("  set state <value>");
+                        pw.println("  reset");
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index fb69c86..8fb2e9f 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -149,6 +149,7 @@
     static final int MSG_BIND_METHOD = 3010;
     static final int MSG_SET_ACTIVE = 3020;
     static final int MSG_SET_CURSOR_ANCHOR_MONITOR_MODE = 3030;
+    static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 3040;
 
     static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
 
@@ -172,7 +173,7 @@
     private final HardKeyboardListener mHardKeyboardListener;
     private final WindowManagerService mWindowManagerService;
 
-    final InputBindResult mNoBinding = new InputBindResult(null, null, null, -1);
+    final InputBindResult mNoBinding = new InputBindResult(null, null, null, -1, -1);
 
     // All known input methods.  mMethodMap also serves as the global
     // lock for this class.
@@ -379,6 +380,8 @@
      */
     boolean mScreenOn = true;
 
+    int mCurUserActionNotificationSequenceNumber = 0;
+
     int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
     int mImeWindowVis;
 
@@ -961,6 +964,19 @@
         return false;
     }
 
+
+    /**
+     * Returns true iff the caller is identified to be the current input method with the token.
+     * @param token The window token given to the input method when it was started.
+     * @return true if and only if non-null valid token is specified.
+     */
+    private boolean calledWithValidToken(IBinder token) {
+        if (token == null || mCurToken != token) {
+            return false;
+        }
+        return true;
+    }
+
     private boolean bindCurrentInputMethodService(
             Intent service, ServiceConnection conn, int flags) {
         if (service == null || conn == null) {
@@ -1116,7 +1132,8 @@
             showCurrentInputLocked(getAppShowFlags(), null);
         }
         return new InputBindResult(session.session,
-                session.channel != null ? session.channel.dup() : null, mCurId, mCurSeq);
+                (session.channel != null ? session.channel.dup() : null),
+                mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
     }
 
     InputBindResult startInputLocked(IInputMethodClient client,
@@ -1192,7 +1209,8 @@
                     // Return to client, and we will get back with it when
                     // we have had a session made for it.
                     requestClientSessionLocked(cs);
-                    return new InputBindResult(null, null, mCurId, mCurSeq);
+                    return new InputBindResult(null, null, mCurId, mCurSeq,
+                            mCurUserActionNotificationSequenceNumber);
                 } else if (SystemClock.uptimeMillis()
                         < (mLastBindTime+TIME_TO_RECONNECT)) {
                     // In this case we have connected to the service, but
@@ -1202,7 +1220,8 @@
                     // we can report back.  If it has been too long, we want
                     // to fall through so we can try a disconnect/reconnect
                     // to see if we can get back in touch with the service.
-                    return new InputBindResult(null, null, mCurId, mCurSeq);
+                    return new InputBindResult(null, null, mCurId, mCurSeq,
+                            mCurUserActionNotificationSequenceNumber);
                 } else {
                     EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
                             mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
@@ -1221,7 +1240,8 @@
         if (!mSystemReady) {
             // If the system is not yet ready, we shouldn't be running third
             // party code.
-            return new InputBindResult(null, null, mCurMethodId, mCurSeq);
+            return new InputBindResult(null, null, mCurMethodId, mCurSeq,
+                    mCurUserActionNotificationSequenceNumber);
         }
 
         InputMethodInfo info = mMethodMap.get(mCurMethodId);
@@ -1250,7 +1270,8 @@
                         WindowManager.LayoutParams.TYPE_INPUT_METHOD);
             } catch (RemoteException e) {
             }
-            return new InputBindResult(null, null, mCurId, mCurSeq);
+            return new InputBindResult(null, null, mCurId, mCurSeq,
+                    mCurUserActionNotificationSequenceNumber);
         } else {
             mCurIntent = null;
             Slog.w(TAG, "Failure connecting to input method service: "
@@ -1429,15 +1450,15 @@
 
     @Override
     public void updateStatusIcon(IBinder token, String packageName, int iconId) {
-        int uid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
         try {
-            if (token == null || mCurToken != token) {
-                Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token);
-                return;
-            }
-
             synchronized (mMethodMap) {
+                if (!calledWithValidToken(token)) {
+                    final int uid = Binder.getCallingUid();
+                    Slog.e(TAG, "Ignoring updateStatusIcon due to an invalid token. uid:" + uid
+                            + " token:" + token);
+                    return;
+                }
                 if (iconId == 0) {
                     if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
                     if (mStatusBar != null) {
@@ -1527,9 +1548,10 @@
     public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
         final long ident = Binder.clearCallingIdentity();
         try {
-            if (token == null || mCurToken != token) {
-                int uid = Binder.getCallingUid();
-                Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token);
+            if (!calledWithValidToken(token)) {
+                final int uid = Binder.getCallingUid();
+                Slog.e(TAG, "Ignoring setImeWindowStatus due to an invalid token. uid:" + uid
+                        + " token:" + token);
                 return;
             }
             synchronized (mMethodMap) {
@@ -1681,6 +1703,11 @@
             mCurMethodId = null;
             unbindCurrentMethodLocked(true, false);
         }
+        // Here is not the perfect place to reset the switching controller. Ideally
+        // mSwitchingController and mSettings should be able to share the same state.
+        // TODO: Make sure that mSwitchingController and mSettings are sharing the
+        // the same enabled IMEs list.
+        mSwitchingController.resetCircularListLocked(mContext);
     }
 
     /* package */ void setInputMethodLocked(String id, int subtypeId) {
@@ -2080,8 +2107,9 @@
         }
         synchronized (mMethodMap) {
             if (subtype != null) {
-                setInputMethodWithSubtypeId(token, id, InputMethodUtils.getSubtypeIdFromHashCode(
-                        mMethodMap.get(id), subtype.hashCode()));
+                setInputMethodWithSubtypeIdLocked(token, id,
+                        InputMethodUtils.getSubtypeIdFromHashCode(mMethodMap.get(id),
+                                subtype.hashCode()));
             } else {
                 setInputMethod(token, id);
             }
@@ -2168,7 +2196,7 @@
                     Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
                             + ", from: " + mCurMethodId + ", " + subtypeId);
                 }
-                setInputMethodWithSubtypeId(token, targetLastImiId, subtypeId);
+                setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId);
                 return true;
             } else {
                 return false;
@@ -2182,12 +2210,19 @@
             return false;
         }
         synchronized (mMethodMap) {
+            if (!calledWithValidToken(token)) {
+                final int uid = Binder.getCallingUid();
+                Slog.e(TAG, "Ignoring switchToNextInputMethod due to an invalid token. uid:" + uid
+                        + " token:" + token);
+                return false;
+            }
             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
                     onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype);
             if (nextSubtype == null) {
                 return false;
             }
-            setInputMethodWithSubtypeId(token, nextSubtype.mImi.getId(), nextSubtype.mSubtypeId);
+            setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
+                    nextSubtype.mSubtypeId);
             return true;
         }
     }
@@ -2198,6 +2233,12 @@
             return false;
         }
         synchronized (mMethodMap) {
+            if (!calledWithValidToken(token)) {
+                final int uid = Binder.getCallingUid();
+                Slog.e(TAG, "Ignoring shouldOfferSwitchingToNextInputMethod due to an invalid "
+                        + "token. uid:" + uid + " token:" + token);
+                return false;
+            }
             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
                     false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype);
             if (nextSubtype == null) {
@@ -2277,14 +2318,22 @@
     }
 
     @Override
-    public void notifyTextCommitted() {
+    public void notifyUserAction(int sequenceNumber) {
         if (DEBUG) {
-            Slog.d(TAG, "Got the notification of commitText");
+            Slog.d(TAG, "Got the notification of a user action. sequenceNumber:" + sequenceNumber);
         }
         synchronized (mMethodMap) {
+            if (mCurUserActionNotificationSequenceNumber != sequenceNumber) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Ignoring the user action notification due to the sequence number "
+                            + "mismatch. expected:" + mCurUserActionNotificationSequenceNumber
+                            + " actual: " + sequenceNumber);
+                }
+                return;
+            }
             final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
             if (imi != null) {
-                mSwitchingController.onCommitTextLocked(imi, mCurrentSubtype);
+                mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
             }
         }
     }
@@ -2298,11 +2347,10 @@
             return;
         }
         synchronized (mMethodMap) {
-            if (token == null || mCurToken != token) {
-                if (DEBUG) {
-                    Slog.w(TAG, "Ignoring setCursorAnchorMonitorMode from uid "
-                            + Binder.getCallingUid() + " token: " + token);
-                }
+            if (!calledWithValidToken(token)) {
+                final int uid = Binder.getCallingUid();
+                Slog.e(TAG, "Ignoring setCursorAnchorMonitorMode due to an invalid token. uid:"
+                        + uid + " token:" + token);
                 return;
             }
             executeOrSendMessage(mCurMethod, mCaller.obtainMessageIO(
@@ -2312,26 +2360,30 @@
 
     private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
         synchronized (mMethodMap) {
-            if (token == null) {
-                if (mContext.checkCallingOrSelfPermission(
-                        android.Manifest.permission.WRITE_SECURE_SETTINGS)
-                        != PackageManager.PERMISSION_GRANTED) {
-                    throw new SecurityException(
-                            "Using null token requires permission "
-                            + android.Manifest.permission.WRITE_SECURE_SETTINGS);
-                }
-            } else if (mCurToken != token) {
-                Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
-                        + " token: " + token);
-                return;
-            }
+            setInputMethodWithSubtypeIdLocked(token, id, subtypeId);
+        }
+    }
 
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                setInputMethodLocked(id, subtypeId);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
+    private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) {
+        if (token == null) {
+            if (mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException(
+                        "Using null token requires permission "
+                        + android.Manifest.permission.WRITE_SECURE_SETTINGS);
             }
+        } else if (mCurToken != token) {
+            Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
+                    + " token: " + token);
+            return;
+        }
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            setInputMethodLocked(id, subtypeId);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -2341,9 +2393,10 @@
             return;
         }
         synchronized (mMethodMap) {
-            if (token == null || mCurToken != token) {
-                if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
-                        + Binder.getCallingUid() + " token: " + token);
+            if (!calledWithValidToken(token)) {
+                final int uid = Binder.getCallingUid();
+                Slog.e(TAG, "Ignoring hideInputMethod due to an invalid token. uid:"
+                        + uid + " token:" + token);
                 return;
             }
             long ident = Binder.clearCallingIdentity();
@@ -2361,9 +2414,10 @@
             return;
         }
         synchronized (mMethodMap) {
-            if (token == null || mCurToken != token) {
-                Slog.w(TAG, "Ignoring showMySoftInput of uid "
-                        + Binder.getCallingUid() + " token: " + token);
+            if (!calledWithValidToken(token)) {
+                final int uid = Binder.getCallingUid();
+                Slog.e(TAG, "Ignoring showMySoftInput due to an invalid token. uid:"
+                        + uid + " token:" + token);
                 return;
             }
             long ident = Binder.clearCallingIdentity();
@@ -2550,6 +2604,20 @@
                             + " uid " + ((ClientState)msg.obj).uid);
                 }
                 return true;
+            case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: {
+                final int sequenceNumber = msg.arg1;
+                final IInputMethodClient client = (IInputMethodClient)msg.obj;
+                try {
+                    client.setUserActionNotificationSequenceNumber(sequenceNumber);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Got RemoteException sending "
+                            + "setUserActionNotificationSequenceNumber("
+                            + sequenceNumber + ") notification to pid "
+                            + ((ClientState)msg.obj).pid + " uid "
+                            + ((ClientState)msg.obj).uid);
+                }
+                return true;
+            }
 
             // --------------------------------------------------------------
             case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
@@ -2650,7 +2718,10 @@
                 setInputMethodEnabledLocked(defaultImiId, true);
             }
         }
-
+        // Here is not the perfect place to reset the switching controller. Ideally
+        // mSwitchingController and mSettings should be able to share the same state.
+        // TODO: Make sure that mSwitchingController and mSettings are sharing the
+        // the same enabled IMEs list.
         mSwitchingController.resetCircularListLocked(mContext);
     }
 
@@ -2958,6 +3029,19 @@
         // Update the history of InputMethod and Subtype
         mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
 
+        mCurUserActionNotificationSequenceNumber =
+                Math.max(mCurUserActionNotificationSequenceNumber + 1, 1);
+        if (DEBUG) {
+            Slog.d(TAG, "Bump mCurUserActionNotificationSequenceNumber:"
+                    + mCurUserActionNotificationSequenceNumber);
+        }
+
+        if (mCurClient != null && mCurClient.client != null) {
+            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
+                    MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER,
+                    mCurUserActionNotificationSequenceNumber, mCurClient.client));
+        }
+
         // Set Subtype here
         if (imi == null || subtypeId < 0) {
             mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
@@ -3449,6 +3533,8 @@
                     + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
                     + " mShowForced=" + mShowForced
                     + " mInputShown=" + mInputShown);
+            p.println("  mCurUserActionNotificationSequenceNumber="
+                    + mCurUserActionNotificationSequenceNumber);
             p.println("  mSystemReady=" + mSystemReady + " mInteractive=" + mScreenOn);
         }
 
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 11fc941..5eee396 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -29,6 +29,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.Signature;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.location.Address;
@@ -55,10 +56,12 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.Slog;
+
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
@@ -144,6 +147,7 @@
     private GeofenceManager mGeofenceManager;
     private PackageManager mPackageManager;
     private PowerManager mPowerManager;
+    private UserManager mUserManager;
     private GeocoderProxy mGeocodeProvider;
     private IGpsStatusProvider mGpsStatusProvider;
     private INetInitiatedListener mNetInitiatedListener;
@@ -197,6 +201,7 @@
 
     // current active user on the device - other users are denied location data
     private int mCurrentUserId = UserHandle.USER_OWNER;
+    private int[] mCurrentUserProfiles = new int[] { UserHandle.USER_OWNER };
 
     public LocationManagerService(Context context) {
         super();
@@ -241,6 +246,9 @@
             };
             mAppOps.startWatchingMode(AppOpsManager.OP_COARSE_LOCATION, null, callback);
 
+            mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+            updateUserProfiles(mCurrentUserId);
+
             // prepare providers
             loadProvidersLocked();
             updateProvidersLocked();
@@ -262,6 +270,8 @@
         // listen for user change
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+        intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+        intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
 
         mContext.registerReceiverAsUser(new BroadcastReceiver() {
             @Override
@@ -269,11 +279,46 @@
                 String action = intent.getAction();
                 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                     switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+                } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
+                        || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
+                    updateUserProfiles(mCurrentUserId);
                 }
             }
         }, UserHandle.ALL, intentFilter, null, mLocationHandler);
     }
 
+    /**
+     * Makes a list of userids that are related to the current user. This is
+     * relevant when using managed profiles. Otherwise the list only contains
+     * the current user.
+     *
+     * @param currentUserId the current user, who might have an alter-ego.
+     */
+    void updateUserProfiles(int currentUserId) {
+        List<UserInfo> profiles = mUserManager.getProfiles(currentUserId);
+        synchronized (mLock) {
+            mCurrentUserProfiles = new int[profiles.size()];
+            for (int i = 0; i < mCurrentUserProfiles.length; i++) {
+                mCurrentUserProfiles[i] = profiles.get(i).id;
+            }
+        }
+    }
+
+    /**
+     * Checks if the specified userId matches any of the current foreground
+     * users stored in mCurrentUserProfiles.
+     */
+    private boolean isCurrentProfile(int userId) {
+        synchronized (mLock) {
+            for (int i = 0; i < mCurrentUserProfiles.length; i++) {
+                if (mCurrentUserProfiles[i] == userId) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
     private void ensureFallbackFusedProviderPresentLocked(ArrayList<String> pkgs) {
         PackageManager pm = mContext.getPackageManager();
         String systemPackageName = mContext.getPackageName();
@@ -427,8 +472,9 @@
         }
 
         // bind to fused provider if supported
-        if (FlpHardwareProvider.getInstance(mContext).isSupported()) {
-          FlpHardwareProvider flpHardwareProvider = FlpHardwareProvider.getInstance(mContext);
+        if (FlpHardwareProvider.isSupported()) {
+          FlpHardwareProvider flpHardwareProvider =
+              FlpHardwareProvider.getInstance(mContext);
           FusedProxy fusedProxy = FusedProxy.createAndBind(
                   mContext,
                   mLocationHandler,
@@ -491,9 +537,10 @@
             mLastLocation.clear();
             mLastLocationCoarseInterval.clear();
             for (LocationProviderInterface p : mProviders) {
-                updateProviderListenersLocked(p.getName(), false, mCurrentUserId);
+                updateProviderListenersLocked(p.getName(), false);
             }
             mCurrentUserId = userId;
+            updateUserProfiles(userId);
             updateProvidersLocked();
         }
     }
@@ -893,7 +940,7 @@
      * @return
      */
     private boolean isAllowedByUserSettingsLocked(String provider, int uid) {
-        if (UserHandle.getUserId(uid) != mCurrentUserId && !isUidALocationProvider(uid)) {
+        if (!isCurrentProfile(UserHandle.getUserId(uid)) && !isUidALocationProvider(uid)) {
             return false;
         }
         return isAllowedByCurrentUserSettingsLocked(provider);
@@ -1180,7 +1227,7 @@
             String name = p.getName();
             boolean shouldBeEnabled = isAllowedByCurrentUserSettingsLocked(name);
             if (isEnabled && !shouldBeEnabled) {
-                updateProviderListenersLocked(name, false, mCurrentUserId);
+                updateProviderListenersLocked(name, false);
                 // If any provider has been disabled, clear all last locations for all providers.
                 // This is to be on the safe side in case a provider has location derived from
                 // this disabled provider.
@@ -1188,7 +1235,7 @@
                 mLastLocationCoarseInterval.clear();
                 changesMade = true;
             } else if (!isEnabled && shouldBeEnabled) {
-                updateProviderListenersLocked(name, true, mCurrentUserId);
+                updateProviderListenersLocked(name, true);
                 changesMade = true;
             }
         }
@@ -1200,7 +1247,7 @@
         }
     }
 
-    private void updateProviderListenersLocked(String provider, boolean enabled, int userId) {
+    private void updateProviderListenersLocked(String provider, boolean enabled) {
         int listeners = 0;
 
         LocationProviderInterface p = mProvidersByName.get(provider);
@@ -1213,7 +1260,7 @@
             final int N = records.size();
             for (int i = 0; i < N; i++) {
                 UpdateRecord record = records.get(i);
-                if (UserHandle.getUserId(record.mReceiver.mUid) == userId) {
+                if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mUid))) {
                     // Sends a notification message to the receiver
                     if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) {
                         if (deadReceivers == null) {
@@ -1252,7 +1299,7 @@
 
         if (records != null) {
             for (UpdateRecord record : records) {
-                if (UserHandle.getUserId(record.mReceiver.mUid) == mCurrentUserId) {
+                if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mUid))) {
                     if (checkLocationAccess(record.mReceiver.mUid, record.mReceiver.mPackageName,
                             record.mReceiver.mAllowedResolutionLevel)) {
                         LocationRequest locationRequest = record.mRequest;
@@ -1273,7 +1320,7 @@
                 // under that threshold.
                 long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2;
                 for (UpdateRecord record : records) {
-                    if (UserHandle.getUserId(record.mReceiver.mUid) == mCurrentUserId) {
+                    if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mUid))) {
                         LocationRequest locationRequest = record.mRequest;
                         if (locationRequest.getInterval() <= thresholdInterval) {
                             if (record.mReceiver.mWorkSource != null
@@ -2017,7 +2064,7 @@
             boolean receiverDead = false;
 
             int receiverUserId = UserHandle.getUserId(receiver.mUid);
-            if (receiverUserId != mCurrentUserId && !isUidALocationProvider(receiver.mUid)) {
+            if (!isCurrentProfile(receiverUserId) && !isUidALocationProvider(receiver.mUid)) {
                 if (D) {
                     Log.d(TAG, "skipping loc update for background user " + receiverUserId +
                             " (current user: " + mCurrentUserId + ", app: " +
diff --git a/services/core/java/com/android/server/WiredAccessoryManager.java b/services/core/java/com/android/server/WiredAccessoryManager.java
index cd8c13fb..d8ee8a1 100644
--- a/services/core/java/com/android/server/WiredAccessoryManager.java
+++ b/services/core/java/com/android/server/WiredAccessoryManager.java
@@ -246,7 +246,8 @@
     private void setDeviceStateLocked(int headset,
             int headsetState, int prevHeadsetState, String headsetName) {
         if ((headsetState & headset) != (prevHeadsetState & headset)) {
-            int device;
+            int outDevice = 0;
+            int inDevice = 0;
             int state;
 
             if ((headsetState & headset) != 0) {
@@ -256,15 +257,16 @@
             }
 
             if (headset == BIT_HEADSET) {
-                device = AudioManager.DEVICE_OUT_WIRED_HEADSET;
+                outDevice = AudioManager.DEVICE_OUT_WIRED_HEADSET;
+                inDevice = AudioManager.DEVICE_IN_WIRED_HEADSET;
             } else if (headset == BIT_HEADSET_NO_MIC){
-                device = AudioManager.DEVICE_OUT_WIRED_HEADPHONE;
+                outDevice = AudioManager.DEVICE_OUT_WIRED_HEADPHONE;
             } else if (headset == BIT_USB_HEADSET_ANLG) {
-                device = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET;
+                outDevice = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET;
             } else if (headset == BIT_USB_HEADSET_DGTL) {
-                device = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET;
+                outDevice = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET;
             } else if (headset == BIT_HDMI_AUDIO) {
-                device = AudioManager.DEVICE_OUT_HDMI;
+                outDevice = AudioManager.DEVICE_OUT_HDMI;
             } else {
                 Slog.e(TAG, "setDeviceState() invalid headset type: "+headset);
                 return;
@@ -273,7 +275,12 @@
             if (LOG)
                 Slog.v(TAG, "device "+headsetName+((state == 1) ? " connected" : " disconnected"));
 
-            mAudioManager.setWiredDeviceConnectionState(device, state, headsetName);
+            if (outDevice != 0) {
+              mAudioManager.setWiredDeviceConnectionState(outDevice, state, headsetName);
+            }
+            if (inDevice != 0) {
+              mAudioManager.setWiredDeviceConnectionState(inDevice, state, headsetName);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 108a079..816e022 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -309,7 +309,7 @@
         }
         ServiceRecord r = res.record;
         NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked(
-                callingUid, r.packageName, service, service.getFlags(), null);
+                callingUid, r.packageName, service, service.getFlags(), null, r.userId);
         if (unscheduleServiceRestartLocked(r, callingUid, false)) {
             if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: " + r);
         }
@@ -989,7 +989,8 @@
                         sInfo.applicationInfo.packageName, sInfo.name);
                 if (userId > 0) {
                     if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo,
-                            sInfo.name, sInfo.flags)) {
+                            sInfo.name, sInfo.flags)
+                            && mAm.isValidSingletonCall(callingUid, sInfo.applicationInfo.uid)) {
                         userId = 0;
                         smap = getServiceMap(0);
                     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bd1baac..697e1f2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static com.android.internal.util.XmlUtils.readBooleanAttribute;
 import static com.android.internal.util.XmlUtils.readIntAttribute;
@@ -283,7 +284,7 @@
     // before we decide it's never going to come up for real, when the process was
     // started with a wrapper for instrumentation (such as Valgrind) because it
     // could take much longer than usual.
-    static final int PROC_START_TIMEOUT_WITH_WRAPPER = 300*1000;
+    static final int PROC_START_TIMEOUT_WITH_WRAPPER = 1200*1000;
 
     // How long to wait after going idle before forcing apps to GC.
     static final int GC_TIMEOUT = 5*1000;
@@ -2189,7 +2190,7 @@
         mBatteryStatsService.publish(mContext);
         mUsageStatsService.publish(mContext);
         mAppOpsService.publish(mContext);
-
+        Slog.d("AppOps", "AppOpsService published");
         LocalServices.addService(ActivityManagerInternal.class, new LocalService());
     }
 
@@ -2833,7 +2834,7 @@
             return app;
         }
 
-        startProcessLocked(app, hostingType, hostingNameStr);
+        startProcessLocked(app, hostingType, hostingNameStr, null /* ABI override */);
         return (app.pid != 0) ? app : null;
     }
 
@@ -2842,7 +2843,7 @@
     }
 
     private final void startProcessLocked(ProcessRecord app,
-            String hostingType, String hostingNameStr) {
+            String hostingType, String hostingNameStr, String abiOverride) {
         if (app.pid > 0 && app.pid != MY_PID) {
             synchronized (mPidsSelfLocked) {
                 mPidsSelfLocked.remove(app.pid);
@@ -2882,16 +2883,17 @@
                 }
 
                 /*
-                 * Add shared application GID so applications can share some
-                 * resources like shared libraries
+                 * Add shared application and profile GIDs so applications can share some
+                 * resources like shared libraries and access user-wide resources
                  */
                 if (permGids == null) {
-                    gids = new int[1];
+                    gids = new int[2];
                 } else {
-                    gids = new int[permGids.length + 1];
-                    System.arraycopy(permGids, 0, gids, 1, permGids.length);
+                    gids = new int[permGids.length + 2];
+                    System.arraycopy(permGids, 0, gids, 2, permGids.length);
                 }
                 gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
+                gids[1] = UserHandle.getUserGid(UserHandle.getUserId(uid));
             }
             if (mFactoryTest != FactoryTest.FACTORY_TEST_OFF) {
                 if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
@@ -2927,7 +2929,7 @@
                 debugFlags |= Zygote.DEBUG_ENABLE_ASSERT;
             }
 
-            String requiredAbi = app.info.cpuAbi;
+            String requiredAbi = (abiOverride != null) ? abiOverride : app.info.cpuAbi;
             if (requiredAbi == null) {
                 requiredAbi = Build.SUPPORTED_ABIS[0];
             }
@@ -2977,6 +2979,10 @@
                 }
             }
             buf.append("}");
+            if (requiredAbi != null) {
+                buf.append(" abi=");
+                buf.append(requiredAbi);
+            }
             Slog.i(TAG, buf.toString());
             app.setPid(startResult.pid);
             app.usingWrapper = startResult.usingWrapper;
@@ -5033,7 +5039,7 @@
 
             if (app.persistent && !app.isolated) {
                 if (!callerWillRestart) {
-                    addAppLocked(app.info, false);
+                    addAppLocked(app.info, false, null /* ABI override */);
                 } else {
                     needRestart = true;
                 }
@@ -5146,7 +5152,7 @@
             app.deathRecipient = adr;
         } catch (RemoteException e) {
             app.resetPackageList(mProcessStats);
-            startProcessLocked(app, "link fail", processName);
+            startProcessLocked(app, "link fail", processName, null /* ABI override */);
             return false;
         }
 
@@ -5239,7 +5245,7 @@
 
             app.resetPackageList(mProcessStats);
             app.unlinkDeathRecipient();
-            startProcessLocked(app, "bind fail", processName);
+            startProcessLocked(app, "bind fail", processName, null /* ABI override */);
             return false;
         }
 
@@ -5390,7 +5396,7 @@
                 for (int ip=0; ip<NP; ip++) {
                     if (DEBUG_PROCESSES) Slog.v(TAG, "Starting process on hold: "
                             + procs.get(ip));
-                    startProcessLocked(procs.get(ip), "on-hold", null);
+                    startProcessLocked(procs.get(ip), "on-hold", null, null /* ABI override */);
                 }
             }
             
@@ -6394,7 +6400,7 @@
      * Like checkGrantUriPermissionLocked, but takes an Intent.
      */
     NeededUriGrants checkGrantUriPermissionFromIntentLocked(int callingUid,
-            String targetPkg, Intent intent, int mode, NeededUriGrants needed) {
+            String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId) {
         if (DEBUG_URI_PERMISSION) Slog.v(TAG,
                 "Checking URI perm to data=" + (intent != null ? intent.getData() : null)
                 + " clip=" + (intent != null ? intent.getClipData() : null)
@@ -6413,11 +6419,28 @@
         if (data == null && clip == null) {
             return null;
         }
-
+        final IPackageManager pm = AppGlobals.getPackageManager();
+        int targetUid;
+        if (needed != null) {
+            targetUid = needed.targetUid;
+        } else {
+            try {
+                targetUid = pm.getPackageUid(targetPkg, targetUserId);
+            } catch (RemoteException ex) {
+                return null;
+            }
+            if (targetUid < 0) {
+                if (DEBUG_URI_PERMISSION) {
+                    Slog.v(TAG, "Can't grant URI permission no uid for: " + targetPkg
+                            + " on user " + targetUserId);
+                }
+                return null;
+            }
+        }
         if (data != null) {
             GrantUri grantUri = GrantUri.resolve(UserHandle.getUserId(callingUid), data);
-            int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, grantUri, mode,
-                    needed != null ? needed.targetUid : -1);
+            targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, grantUri, mode,
+                    targetUid);
             if (targetUid > 0) {
                 if (needed == null) {
                     needed = new NeededUriGrants(targetPkg, targetUid, mode);
@@ -6429,10 +6452,9 @@
             for (int i=0; i<clip.getItemCount(); i++) {
                 Uri uri = clip.getItemAt(i).getUri();
                 if (uri != null) {
-                    int targetUid = -1;
                     GrantUri grantUri = GrantUri.resolve(UserHandle.getUserId(callingUid), uri);
                     targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, grantUri, mode,
-                            needed != null ? needed.targetUid : -1);
+                            targetUid);
                     if (targetUid > 0) {
                         if (needed == null) {
                             needed = new NeededUriGrants(targetPkg, targetUid, mode);
@@ -6443,7 +6465,7 @@
                     Intent clipIntent = clip.getItemAt(i).getIntent();
                     if (clipIntent != null) {
                         NeededUriGrants newNeeded = checkGrantUriPermissionFromIntentLocked(
-                                callingUid, targetPkg, clipIntent, mode, needed);
+                                callingUid, targetPkg, clipIntent, mode, needed, targetUserId);
                         if (newNeeded != null) {
                             needed = newNeeded;
                         }
@@ -6470,9 +6492,9 @@
     }
 
     void grantUriPermissionFromIntentLocked(int callingUid,
-            String targetPkg, Intent intent, UriPermissionOwner owner) {
+            String targetPkg, Intent intent, UriPermissionOwner owner, int targetUserId) {
         NeededUriGrants needed = checkGrantUriPermissionFromIntentLocked(callingUid, targetPkg,
-                intent, intent != null ? intent.getFlags() : 0, null);
+                intent, intent != null ? intent.getFlags() : 0, null, targetUserId);
         if (needed == null) {
             return;
         }
@@ -7751,7 +7773,7 @@
      * in {@link ContentProvider}.
      */
     private final String checkContentProviderPermissionLocked(
-            ProviderInfo cpi, ProcessRecord r, int userId) {
+            ProviderInfo cpi, ProcessRecord r, int userId, boolean checkUser) {
         final int callingPid = (r != null) ? r.pid : Binder.getCallingPid();
         final int callingUid = (r != null) ? r.uid : Binder.getCallingUid();
         final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
@@ -7768,8 +7790,10 @@
                 }
             }
         }
-        userId = handleIncomingUser(callingPid, callingUid, userId,
-                false, true, "checkContentProviderPermissionLocked", null);
+        if (checkUser) {
+            userId = handleIncomingUser(callingPid, callingUid, userId,
+                    false, true, "checkContentProviderPermissionLocked " + cpi.authority, null);
+        }
         if (checkComponentPermission(cpi.readPermission, callingPid, callingUid,
                 cpi.applicationInfo.uid, cpi.exported)
                 == PackageManager.PERMISSION_GRANTED) {
@@ -7904,13 +7928,34 @@
                 }
             }
 
+            boolean checkCrossUser = true;
+
             // First check if this content provider has been published...
             cpr = mProviderMap.getProviderByName(name, userId);
+            // If that didn't work, check if it exists for user 0 and then
+            // verify that it's a singleton provider before using it.
+            if (cpr == null && userId != UserHandle.USER_OWNER) {
+                cpr = mProviderMap.getProviderByName(name, UserHandle.USER_OWNER);
+                if (cpr != null) {
+                    cpi = cpr.info;
+                    if (isSingleton(cpi.processName, cpi.applicationInfo,
+                            cpi.name, cpi.flags)
+                            && isValidSingletonCall(r.uid, cpi.applicationInfo.uid)) {
+                        userId = UserHandle.USER_OWNER;
+                        checkCrossUser = false;
+                    } else {
+                        cpr = null;
+                        cpi = null;
+                    }
+                }
+            }
+
             boolean providerRunning = cpr != null;
             if (providerRunning) {
                 cpi = cpr.info;
                 String msg;
-                if ((msg=checkContentProviderPermissionLocked(cpi, r, userId)) != null) {
+                if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser))
+                        != null) {
                     throw new SecurityException(msg);
                 }
 
@@ -7990,15 +8035,21 @@
                 if (cpi == null) {
                     return null;
                 }
+                // If the provider is a singleton AND
+                // (it's a call within the same user || the provider is a
+                // privileged app)
+                // Then allow connecting to the singleton provider
                 singleton = isSingleton(cpi.processName, cpi.applicationInfo,
-                        cpi.name, cpi.flags); 
+                        cpi.name, cpi.flags)
+                        && isValidSingletonCall(r.uid, cpi.applicationInfo.uid);
                 if (singleton) {
-                    userId = 0;
+                    userId = UserHandle.USER_OWNER;
                 }
                 cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);
 
                 String msg;
-                if ((msg=checkContentProviderPermissionLocked(cpi, r, userId)) != null) {
+                if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, !singleton))
+                        != null) {
                     throw new SecurityException(msg);
                 }
 
@@ -8534,7 +8585,8 @@
         return new ProcessRecord(stats, info, proc, uid);
     }
 
-    final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated) {
+    final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated,
+            String abiOverride) {
         ProcessRecord app;
         if (!isolated) {
             app = getProcessRecordLocked(info.processName, info.uid, true);
@@ -8569,7 +8621,8 @@
         }
         if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) {
             mPersistentStartingProcesses.add(app);
-            startProcessLocked(app, "added application", app.processName);
+            startProcessLocked(app, "added application", app.processName,
+                    abiOverride);
         }
 
         return app;
@@ -9786,7 +9839,7 @@
                                 = (ApplicationInfo)apps.get(i);
                             if (info != null &&
                                     !info.packageName.equals("android")) {
-                                addAppLocked(info, false);
+                                addAppLocked(info, false, null /* ABI override */);
                             }
                         }
                     }
@@ -10577,8 +10630,7 @@
         // assume our apps are happy - lazy create the list
         List<ActivityManager.ProcessErrorStateInfo> errList = null;
 
-        final boolean allUsers = ActivityManager.checkUidPermission(
-                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+        final boolean allUsers = ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL,
                 Binder.getCallingUid()) == PackageManager.PERMISSION_GRANTED;
         int userId = UserHandle.getUserId(Binder.getCallingUid());
 
@@ -10667,8 +10719,7 @@
         enforceNotIsolatedCaller("getRunningAppProcesses");
         // Lazy instantiation of list
         List<ActivityManager.RunningAppProcessInfo> runList = null;
-        final boolean allUsers = ActivityManager.checkUidPermission(
-                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+        final boolean allUsers = ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL,
                 Binder.getCallingUid()) == PackageManager.PERMISSION_GRANTED;
         int userId = UserHandle.getUserId(Binder.getCallingUid());
         synchronized (this) {
@@ -12968,7 +13019,7 @@
             // We have components that still need to be running in the
             // process, so re-launch it.
             mProcessNames.put(app.processName, app.uid, app);
-            startProcessLocked(app, "restart", app.processName);
+            startProcessLocked(app, "restart", app.processName, null /* ABI override */);
         } else if (app.pid > 0 && app.pid != MY_PID) {
             // Goodbye!
             boolean removed;
@@ -13108,8 +13159,7 @@
                 if ((requireFull || checkComponentPermission(
                         android.Manifest.permission.INTERACT_ACROSS_USERS,
                         callingPid, callingUid, -1, true) != PackageManager.PERMISSION_GRANTED)
-                        && checkComponentPermission(
-                                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                        && checkComponentPermission(INTERACT_ACROSS_USERS_FULL,
                                 callingPid, callingUid, -1, true)
                                 != PackageManager.PERMISSION_GRANTED) {
                     if (userId == UserHandle.USER_CURRENT_OR_SELF) {
@@ -13129,7 +13179,7 @@
                         builder.append(" but is calling from user ");
                         builder.append(UserHandle.getUserId(callingUid));
                         builder.append("; this requires ");
-                        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+                        builder.append(INTERACT_ACROSS_USERS_FULL);
                         if (!requireFull) {
                             builder.append(" or ");
                             builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
@@ -13159,8 +13209,9 @@
     boolean isSingleton(String componentProcessName, ApplicationInfo aInfo,
             String className, int flags) {
         boolean result = false;
+        // For apps that don't have pre-defined UIDs, check for permission
         if (UserHandle.getAppId(aInfo.uid) >= Process.FIRST_APPLICATION_UID) {
-            if ((flags&ServiceInfo.FLAG_SINGLE_USER) != 0) {
+            if ((flags & ServiceInfo.FLAG_SINGLE_USER) != 0) {
                 if (ActivityManager.checkUidPermission(
                         android.Manifest.permission.INTERACT_ACROSS_USERS,
                         aInfo.uid) != PackageManager.PERMISSION_GRANTED) {
@@ -13171,12 +13222,14 @@
                     Slog.w(TAG, msg);
                     throw new SecurityException(msg);
                 }
+                // Permission passed
                 result = true;
             }
-        } else if (componentProcessName == aInfo.packageName) {
-            result = (aInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0;
         } else if ("system".equals(componentProcessName)) {
             result = true;
+        } else {
+            // App with pre-defined UID, check if it's a persistent app
+            result = (aInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0;
         }
         if (DEBUG_MU) {
             Slog.v(TAG, "isSingleton(" + componentProcessName + ", " + aInfo
@@ -13185,6 +13238,21 @@
         return result;
     }
 
+    /**
+     * Checks to see if the caller is in the same app as the singleton
+     * component, or the component is in a special app. It allows special apps
+     * to export singleton components but prevents exporting singleton
+     * components for regular apps.
+     */
+    boolean isValidSingletonCall(int callingUid, int componentUid) {
+        int componentAppId = UserHandle.getAppId(componentUid);
+        return UserHandle.isSameApp(callingUid, componentUid)
+                || componentAppId == Process.SYSTEM_UID
+                || componentAppId == Process.PHONE_UID
+                || ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL, componentUid)
+                        == PackageManager.PERMISSION_GRANTED;
+    }
+
     public int bindService(IApplicationThread caller, IBinder token,
             Intent service, String resolvedType,
             IServiceConnection connection, int flags, int userId) {
@@ -13731,8 +13799,8 @@
          */
         int callingAppId = UserHandle.getAppId(callingUid);
         if (callingAppId == Process.SYSTEM_UID || callingAppId == Process.PHONE_UID
-            || callingAppId == Process.SHELL_UID || callingAppId == Process.BLUETOOTH_UID ||
-            callingUid == 0) {
+                || callingAppId == Process.SHELL_UID || callingAppId == Process.BLUETOOTH_UID
+                || callingUid == 0) {
             // Always okay.
         } else if (callerApp == null || !callerApp.persistent) {
             try {
@@ -14265,7 +14333,7 @@
     public boolean startInstrumentation(ComponentName className,
             String profileFile, int flags, Bundle arguments,
             IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection,
-            int userId) {
+            int userId, String abiOverride) {
         enforceNotIsolatedCaller("startInstrumentation");
         userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                 userId, false, true, "startInstrumentation", null);
@@ -14314,7 +14382,7 @@
             // Instrumentation can kill and relaunch even persistent processes
             forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, false, userId,
                     "start instr");
-            ProcessRecord app = addAppLocked(ai, false);
+            ProcessRecord app = addAppLocked(ai, false, abiOverride);
             app.instrumentationClass = className;
             app.instrumentationInfo = ai;
             app.instrumentationProfileFile = profileFile;
@@ -16278,7 +16346,7 @@
                     
                     if (app.persistent) {
                         if (app.persistent) {
-                            addAppLocked(app.info, false);
+                            addAppLocked(app.info, false, null /* ABI override */);
                         }
                     }
                 }
@@ -16542,12 +16610,12 @@
     }
 
     private boolean startUser(final int userId, boolean foreground) {
-        if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+        if (checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
                 != PackageManager.PERMISSION_GRANTED) {
             String msg = "Permission Denial: switchUser() from pid="
                     + Binder.getCallingPid()
                     + ", uid=" + Binder.getCallingUid()
-                    + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+                    + " requires " + INTERACT_ACROSS_USERS_FULL;
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
@@ -16913,12 +16981,12 @@
 
     @Override
     public int stopUser(final int userId, final IStopUserCallback callback) {
-        if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+        if (checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
                 != PackageManager.PERMISSION_GRANTED) {
             String msg = "Permission Denial: switchUser() from pid="
                     + Binder.getCallingPid()
                     + ", uid=" + Binder.getCallingUid()
-                    + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+                    + " requires " + INTERACT_ACROSS_USERS_FULL;
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
@@ -17056,7 +17124,7 @@
     public UserInfo getCurrentUser() {
         if ((checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
                 != PackageManager.PERMISSION_GRANTED) && (
-                checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
                 != PackageManager.PERMISSION_GRANTED)) {
             String msg = "Permission Denial: getCurrentUser() from pid="
                     + Binder.getCallingPid()
@@ -17142,12 +17210,12 @@
 
     @Override
     public void registerUserSwitchObserver(IUserSwitchObserver observer) {
-        if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+        if (checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
                 != PackageManager.PERMISSION_GRANTED) {
             String msg = "Permission Denial: registerUserSwitchObserver() from pid="
                     + Binder.getCallingPid()
                     + ", uid=" + Binder.getCallingUid()
-                    + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+                    + " requires " + INTERACT_ACROSS_USERS_FULL;
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index fe49371..11f855e 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -635,7 +635,7 @@
     final void deliverNewIntentLocked(int callingUid, Intent intent) {
         // The activity now gets access to the data associated with this Intent.
         service.grantUriPermissionFromIntentLocked(callingUid, packageName,
-                intent, getUriPermissionsLocked());
+                intent, getUriPermissionsLocked(), userId);
         // We want to immediately deliver the intent to the activity if
         // it is currently the top resumed activity...  however, if the
         // device is sleeping, then all activities are stopped, so in that
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index d5a50e7..03ce530 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -32,12 +32,12 @@
 
 import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE;
 import static com.android.server.am.ActivityStackSupervisor.DEBUG_APP;
+import static com.android.server.am.ActivityStackSupervisor.DEBUG_CONTAINERS;
 import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE;
 import static com.android.server.am.ActivityStackSupervisor.DEBUG_SCREENSHOTS;
 import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES;
 import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
 
-import android.service.voice.IVoiceInteractionSession;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.server.Watchdog;
@@ -74,6 +74,7 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.service.voice.IVoiceInteractionSession;
 import android.util.EventLog;
 import android.util.Slog;
 import android.view.Display;
@@ -1405,7 +1406,7 @@
 
         ActivityRecord parent = mActivityContainer.mParentActivity;
         if ((parent != null && parent.state != ActivityState.RESUMED) ||
-                !mActivityContainer.isAttached()) {
+                !mActivityContainer.isAttachedLocked()) {
             // Do not resume this stack if its parent is not resumed.
             // TODO: If in a loop, make sure that parent stack resumeTopActivity is called 1st.
             return false;
@@ -2355,7 +2356,7 @@
 
         if (callingUid > 0) {
             mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
-                    data, r.getUriPermissionsLocked());
+                    data, r.getUriPermissionsLocked(), r.userId);
         }
 
         if (DEBUG_RESULTS) Slog.v(TAG, "Send activity result to " + r
@@ -2538,10 +2539,15 @@
             if (DEBUG_RESULTS) Slog.v(TAG, "Adding result to " + resultTo
                     + " who=" + r.resultWho + " req=" + r.requestCode
                     + " res=" + resultCode + " data=" + resultData);
+            if (resultTo.userId != r.userId) {
+                if (resultData != null) {
+                    resultData.prepareToLeaveUser(r.userId);
+                }
+            }
             if (r.info.applicationInfo.uid > 0) {
                 mService.grantUriPermissionFromIntentLocked(r.info.applicationInfo.uid,
                         resultTo.packageName, resultData,
-                        resultTo.getUriPermissionsLocked());
+                        resultTo.getUriPermissionsLocked(), resultTo.userId);
             }
             resultTo.addResultLocked(r, r.resultWho, r.requestCode, resultCode,
                                      resultData);
@@ -2672,11 +2678,14 @@
                 || prevState == ActivityState.INITIALIZING) {
             // If this activity is already stopped, we can just finish
             // it right now.
-            boolean activityRemoved = destroyActivityLocked(r, true,
-                    oomAdj, "finish-imm");
+            r.makeFinishing();
+            boolean activityRemoved = destroyActivityLocked(r, true, oomAdj, "finish-imm");
             if (activityRemoved) {
                 mStackSupervisor.resumeTopActivitiesLocked();
             }
+            if (DEBUG_CONTAINERS) Slog.d(TAG, 
+                    "destroyActivityLocked: finishCurrentActivityLocked r=" + r +
+                    " destroy returned removed=" + activityRemoved);
             return activityRemoved ? null : r;
         }
 
@@ -3039,6 +3048,7 @@
             if (r != null) {
                 mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r);
             }
+            if (DEBUG_CONTAINERS) Slog.d(TAG, "activityDestroyedLocked: r=" + r);
 
             if (isInStackLocked(token) != null) {
                 if (r.state == ActivityState.DESTROYING) {
@@ -3798,6 +3808,7 @@
                 mStacks.remove(this);
                 mStacks.add(0, this);
             }
+            mActivityContainer.onTaskListEmptyLocked();
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 196e860..545a9f7 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -111,6 +111,7 @@
     static final boolean DEBUG_STATES = DEBUG || false;
     static final boolean DEBUG_IDLE = DEBUG || false;
     static final boolean DEBUG_SCREENSHOTS = DEBUG || false;
+    static final boolean DEBUG_CONTAINERS = DEBUG || false;
 
     public static final int HOME_STACK_ID = 0;
 
@@ -134,6 +135,8 @@
     static final int CONTAINER_CALLBACK_VISIBILITY = FIRST_SUPERVISOR_STACK_MSG + 8;
     static final int LOCK_TASK_START_MSG = FIRST_SUPERVISOR_STACK_MSG + 9;
     static final int LOCK_TASK_END_MSG = FIRST_SUPERVISOR_STACK_MSG + 10;
+    static final int CONTAINER_CALLBACK_TASK_LIST_EMPTY = FIRST_SUPERVISOR_STACK_MSG + 11;
+    static final int CONTAINER_TASK_LIST_EMPTY_TIMEOUT = FIRST_SUPERVISOR_STACK_MSG + 12;
 
     private final static String VIRTUAL_DISPLAY_BASE_NAME = "ActivityViewVirtualDisplay";
 
@@ -238,7 +241,7 @@
 
     // TODO: Add listener for removal of references.
     /** Mapping from (ActivityStack/TaskStack).mStackId to their current state */
-    SparseArray<ActivityContainer> mActivityContainers = new SparseArray<ActivityContainer>();
+    private SparseArray<ActivityContainer> mActivityContainers = new SparseArray<ActivityContainer>();
 
     /** Mapping from displayId to display current state */
     private final SparseArray<ActivityDisplay> mActivityDisplays =
@@ -1934,7 +1937,7 @@
         }
 
         mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
-                intent, r.getUriPermissionsLocked());
+                intent, r.getUriPermissionsLocked(), r.userId);
 
         if (newTask) {
             EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId);
@@ -2254,8 +2257,10 @@
 
     ActivityContainer createActivityContainer(ActivityRecord parentActivity,
             IActivityContainerCallback callback) {
-        ActivityContainer activityContainer = new VirtualActivityContainer(parentActivity, callback);
+        ActivityContainer activityContainer =
+                new VirtualActivityContainer(parentActivity, callback);
         mActivityContainers.put(activityContainer.mStackId, activityContainer);
+        if (DEBUG_CONTAINERS) Slog.d(TAG, "createActivityContainer: " + activityContainer);
         parentActivity.mChildContainers.add(activityContainer);
         return activityContainer;
     }
@@ -2264,6 +2269,8 @@
         final ArrayList<ActivityContainer> childStacks = parentActivity.mChildContainers;
         for (int containerNdx = childStacks.size() - 1; containerNdx >= 0; --containerNdx) {
             ActivityContainer container = childStacks.remove(containerNdx);
+            if (DEBUG_CONTAINERS) Slog.d(TAG, "removeChildActivityContainers: removing " +
+                    container);
             container.release();
         }
     }
@@ -2271,11 +2278,8 @@
     void deleteActivityContainer(IActivityContainer container) {
         ActivityContainer activityContainer = (ActivityContainer)container;
         if (activityContainer != null) {
-            activityContainer.mStack.finishAllActivitiesLocked();
-            final ActivityRecord parent = activityContainer.mParentActivity;
-            if (parent != null) {
-                parent.mChildContainers.remove(activityContainer);
-            }
+            if (DEBUG_CONTAINERS) Slog.d(TAG, "deleteActivityContainer: ",
+                    new RuntimeException("here").fillInStackTrace());
             final int stackId = activityContainer.mStackId;
             mActivityContainers.remove(stackId);
             mWindowManager.removeStack(stackId);
@@ -3085,12 +3089,14 @@
                 } break;
                 case CONTAINER_CALLBACK_VISIBILITY: {
                     final ActivityContainer container = (ActivityContainer) msg.obj;
-                    try {
-                        // We only send this message if mCallback is non-null.
-                        container.mCallback.setVisible(container.asBinder(), msg.arg1 == 1);
-                    } catch (RemoteException e) {
+                    final IActivityContainerCallback callback = container.mCallback;
+                    if (callback != null) {
+                        try {
+                            callback.setVisible(container.asBinder(), msg.arg1 == 1);
+                        } catch (RemoteException e) {
+                        }
                     }
-                }
+                } break;
                 case LOCK_TASK_START_MSG: {
                     // When lock task starts, we disable the status bars.
                     try {
@@ -3102,8 +3108,7 @@
                     } catch (RemoteException ex) {
                         throw new RuntimeException(ex);
                     }
-                    break;
-                }
+                } break;
                 case LOCK_TASK_END_MSG: {
                     // When lock task ends, we enable the status bars.
                     try {
@@ -3115,15 +3120,31 @@
                     } catch (RemoteException ex) {
                         throw new RuntimeException(ex);
                     }
-                    break;
-                }
+                } break;
+                case CONTAINER_CALLBACK_TASK_LIST_EMPTY: {
+                    final ActivityContainer container = (ActivityContainer) msg.obj;
+                    final IActivityContainerCallback callback = container.mCallback;
+                    if (callback != null) {
+                        try {
+                            callback.onAllActivitiesComplete(container.asBinder());
+                        } catch (RemoteException e) {
+                        }
+                    }
+                } break;
+                case CONTAINER_TASK_LIST_EMPTY_TIMEOUT: {
+                    synchronized (mService) {
+                        Slog.w(TAG, "Timeout waiting for all activities in task to finish. " +
+                                msg.obj);
+                        ((ActivityContainer) msg.obj).onTaskListEmptyLocked();
+                    }
+                } break;
             }
         }
     }
 
     class ActivityContainer extends android.app.IActivityContainer.Stub {
         final static int FORCE_NEW_TASK_FLAGS = Intent.FLAG_ACTIVITY_NEW_TASK |
-                Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+                Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION;
         final int mStackId;
         IActivityContainerCallback mCallback = null;
         final ActivityStack mStack;
@@ -3173,8 +3194,10 @@
 
         @Override
         public int getDisplayId() {
-            if (mActivityDisplay != null) {
-                return mActivityDisplay.mDisplayId;
+            synchronized (mService) {
+                if (mActivityDisplay != null) {
+                    return mActivityDisplay.mDisplayId;
+                }
             }
             return -1;
         }
@@ -3183,10 +3206,12 @@
         public boolean injectEvent(InputEvent event) {
             final long origId = Binder.clearCallingIdentity();
             try {
-                if (mActivityDisplay != null) {
-                    return mInputManagerInternal.injectInputEvent(event,
-                            mActivityDisplay.mDisplayId,
-                            InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+                synchronized (mService) {
+                    if (mActivityDisplay != null) {
+                        return mInputManagerInternal.injectInputEvent(event,
+                                mActivityDisplay.mDisplayId,
+                                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+                    }
                 }
                 return false;
             } finally {
@@ -3196,10 +3221,16 @@
 
         @Override
         public void release() {
-            mContainerState = CONTAINER_STATE_FINISHING;
-            mStack.finishAllActivitiesLocked();
-            detachLocked();
-            mWindowManager.removeStack(mStackId);
+            synchronized (mService) {
+                if (mContainerState == CONTAINER_STATE_FINISHING) {
+                    return;
+                }
+                mContainerState = CONTAINER_STATE_FINISHING;
+                final Message msg =
+                        mHandler.obtainMessage(CONTAINER_TASK_LIST_EMPTY_TIMEOUT, this);
+                mHandler.sendMessageDelayed(msg, 1000);
+                mStack.finishAllActivitiesLocked();
+            }
         }
 
         private void detachLocked() {
@@ -3290,15 +3321,17 @@
             return ActivityStackSupervisor.this;
         }
 
-        boolean isAttached() {
+        boolean isAttachedLocked() {
             return mActivityDisplay != null;
         }
 
         void getBounds(Point outBounds) {
-            if (mActivityDisplay != null) {
-                mActivityDisplay.getBounds(outBounds);
-            } else {
-                outBounds.set(0, 0);
+            synchronized (mService) {
+                    if (mActivityDisplay != null) {
+                    mActivityDisplay.getBounds(outBounds);
+                } else {
+                    outBounds.set(0, 0);
+                }
             }
         }
 
@@ -3321,6 +3354,15 @@
             return true;
         }
 
+        void onTaskListEmptyLocked() {
+            mHandler.removeMessages(CONTAINER_TASK_LIST_EMPTY_TIMEOUT, this);
+            if (!mStack.isHomeStack()) {
+                detachLocked();
+                deleteActivityContainer(this);
+            }
+            mHandler.obtainMessage(CONTAINER_CALLBACK_TASK_LIST_EMPTY, this).sendToTarget();
+        }
+
         @Override
         public String toString() {
             return mIdString + (mActivityDisplay == null ? "N" : "A");
@@ -3336,7 +3378,7 @@
             mParentActivity = parent;
             mCallback = callback;
             mContainerState = CONTAINER_STATE_NO_SURFACE;
-            mIdString = "VirtualActivtyContainer{" + mStackId + ", parent=" + mParentActivity + "}";
+            mIdString = "VirtualActivityContainer{" + mStackId + ", parent=" + mParentActivity + "}";
         }
 
         @Override
@@ -3382,22 +3424,22 @@
                 }
             }
 
-            setSurfaceIfReady();
+            setSurfaceIfReadyLocked();
 
             if (DEBUG_STACK) Slog.d(TAG, "setSurface: " + this + " to display="
                     + virtualActivityDisplay);
         }
 
         @Override
-        boolean isAttached() {
-            return mSurface != null && super.isAttached();
+        boolean isAttachedLocked() {
+            return mSurface != null && super.isAttachedLocked();
         }
 
         @Override
         void setDrawn() {
             synchronized (mService) {
                 mDrawn = true;
-                setSurfaceIfReady();
+                setSurfaceIfReadyLocked();
             }
         }
 
@@ -3407,8 +3449,8 @@
             return false;
         }
 
-        private void setSurfaceIfReady() {
-            if (DEBUG_STACK) Slog.v(TAG, "setSurfaceIfReady: mDrawn=" + mDrawn +
+        private void setSurfaceIfReadyLocked() {
+            if (DEBUG_STACK) Slog.v(TAG, "setSurfaceIfReadyLocked: mDrawn=" + mDrawn +
                     " mContainerState=" + mContainerState + " mSurface=" + mSurface);
             if (mDrawn && mSurface != null && mContainerState == CONTAINER_STATE_NO_SURFACE) {
                 ((VirtualActivityDisplay) mActivityDisplay).setSurface(mSurface);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 9d6481a..7b2b04d 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -27,6 +27,7 @@
 import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
@@ -858,7 +859,10 @@
             r.state = BroadcastRecord.APP_RECEIVE;
             String targetProcess = info.activityInfo.processName;
             r.curComponent = component;
-            if (r.callingUid != Process.SYSTEM_UID && isSingleton) {
+            final int receiverUid = info.activityInfo.applicationInfo.uid;
+            // If it's a singleton, it needs to be the same app or a special app
+            if (r.callingUid != Process.SYSTEM_UID && isSingleton
+                    && mService.isValidSingletonCall(r.callingUid, receiverUid)) {
                 info.activityInfo = mService.getActivityInfoForUser(info.activityInfo, 0);
             }
             r.curReceiver = info.activityInfo;
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index bdf4708..ec3389b 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -489,8 +489,10 @@
     }
 
     private class StateReceiver extends BroadcastReceiver {
+        @Override
         public void onReceive(Context content, Intent intent) {
             String action = intent.getAction();
+            if (action == null) { return; }
             if (action.equals(UsbManager.ACTION_USB_STATE)) {
                 synchronized (Tethering.this.mPublicSync) {
                     boolean usbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index c502bf3..3b55bfc 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -230,7 +230,7 @@
         // Notify for any user other than the caller's own requires permission.
         final int callingUserHandle = UserHandle.getCallingUserId();
         if (userHandle != callingUserHandle) {
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+            mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS,
                     "no permission to notify other users");
         }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index a98c340..654b574 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -555,8 +555,14 @@
                 // Turn the screen on.  The contents of the screen may not yet
                 // be visible if the electron beam has not been dismissed because
                 // its last frame of animation is solid black.
-                setScreenState(mPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_DOZE
-                        ? Display.STATE_DOZING : Display.STATE_ON);
+
+                if (mPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_DOZE) {
+                    if (!mScreenBrightnessRampAnimator.isAnimating()) {
+                        setScreenState(Display.STATE_DOZING);
+                    }
+                } else {
+                    setScreenState(Display.STATE_ON);
+                }
 
                 if (mPowerRequest.blockScreenOn
                         && mPowerState.getElectronBeamLevel() == 0.0f) {
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 7f43e43..e80aecd 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -111,12 +111,15 @@
         }
     }
 
-    static boolean shouldBlank(int state) {
-        return state == Display.STATE_OFF;
-    }
-
-    static boolean shouldUnblank(int state) {
-        return state == Display.STATE_ON || state == Display.STATE_DOZING;
+    static int getPowerModeForState(int state) {
+        switch (state) {
+            case Display.STATE_OFF:
+                return SurfaceControl.POWER_MODE_OFF;
+            case Display.STATE_DOZING:
+                return SurfaceControl.POWER_MODE_DOZE;
+            default:
+                return SurfaceControl.POWER_MODE_NORMAL;
+        }
     }
 
     private final class LocalDisplayDevice extends DisplayDevice {
@@ -204,11 +207,8 @@
         @Override
         public void requestDisplayStateLocked(int state) {
             if (mState != state) {
-                if (shouldBlank(state) && !shouldBlank(mState)) {
-                    SurfaceControl.blankDisplay(getDisplayTokenLocked());
-                } else if (shouldUnblank(state) && !shouldUnblank(mState)) {
-                    SurfaceControl.unblankDisplay(getDisplayTokenLocked());
-                }
+                SurfaceControl.setDisplayPowerMode(getDisplayTokenLocked(),
+                        getPowerModeForState(state));
                 mState = state;
                 updateDeviceInfoLocked();
             }
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index 579f89f..d36fc2c 100644
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -95,10 +95,10 @@
     private int mProcessedDeviceCount = 0;
 
     /**
-     * @Constructor
+     * Constructor.
      *
-     * @param service
-     * @param sourceAddress
+     * @param service an instance of {@link HdmiControlService}.
+     * @param sourceAddress a logical address which initiates this action
      */
     DeviceDiscoveryAction(HdmiControlService service, int sourceAddress,
             DeviceDiscoveryCallback callback) {
@@ -124,7 +124,7 @@
                 allocateDevices(ackedAddress);
                 startPhysicalAddressStage();
             }
-        }, DEVICE_POLLING_RETRY);
+        }, HdmiControlService.POLL_STRATEGY_REMOTES_DEVICES, DEVICE_POLLING_RETRY);
         return true;
     }
 
@@ -154,6 +154,11 @@
         }
 
         mActionTimer.clearTimerMessage();
+
+        // Check cache first and send request if not exist.
+        if (mayProcessMessageIfCached(address, HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS)) {
+            return;
+        }
         sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(mSourceAddress, address));
         addTimer(mState, TIMEOUT_MS);
     }
@@ -173,6 +178,10 @@
         }
 
         mActionTimer.clearTimerMessage();
+
+        if (mayProcessMessageIfCached(address, HdmiCec.MESSAGE_SET_OSD_NAME)) {
+            return;
+        }
         sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(mSourceAddress, address));
         addTimer(mState, TIMEOUT_MS);
     }
@@ -193,10 +202,23 @@
         }
 
         mActionTimer.clearTimerMessage();
+
+        if (mayProcessMessageIfCached(address, HdmiCec.MESSAGE_DEVICE_VENDOR_ID)) {
+            return;
+        }
         sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(mSourceAddress, address));
         addTimer(mState, TIMEOUT_MS);
     }
 
+    private boolean mayProcessMessageIfCached(int address, int opcode) {
+        HdmiCecMessage message = mService.getCecMessageCache().getMessage(address, opcode);
+        if (message != null) {
+            processCommand(message);
+            return true;
+        }
+        return false;
+    }
+
     @Override
     boolean processCommand(HdmiCecMessage cmd) {
         switch (mState) {
@@ -237,7 +259,7 @@
 
         byte params[] = cmd.getParams();
         if (params.length == 3) {
-            current.mPhysicalAddress = ((params[0] & 0xFF) << 8) | (params[1] & 0xFF);
+            current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params);
             current.mDeviceType = params[2] & 0xFF;
 
             increaseProcessedDeviceCount();
@@ -285,9 +307,7 @@
 
         byte[] params = cmd.getParams();
         if (params.length == 3) {
-            int vendorId = ((params[0] & 0xFF) << 16)
-                    | ((params[1] & 0xFF) << 8)
-                    | (params[2] & 0xFF);
+            int vendorId = HdmiUtils.threeBytesToInt(params);
             current.mVendorId = vendorId;
         } else {
             Slog.w(TAG, "Invalid vendor id: " + cmd.toString());
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index d0b716d..f869424 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -25,6 +25,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.util.Predicate;
 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
 
 import libcore.util.EmptyArray;
@@ -47,6 +48,21 @@
 final class HdmiCecController {
     private static final String TAG = "HdmiCecController";
 
+    /**
+     * Interface to report allocated logical address.
+     */
+    interface AllocateAddressCallback {
+        /**
+         * Called when a new logical address is allocated.
+         *
+         * @param deviceType requested device type to allocate logical address
+         * @param logicalAddress allocated logical address. If it is
+         *                       {@link HdmiCec#ADDR_UNREGISTERED}, it means that
+         *                       it failed to allocate logical address for the given device type
+         */
+        void onAllocated(int deviceType, int logicalAddress);
+    }
+
     private static final byte[] EMPTY_BODY = EmptyArray.BYTE;
 
     // A message to pass cec send command to IO looper.
@@ -63,6 +79,22 @@
 
     private static final int RETRY_COUNT_FOR_LOGICAL_ADDRESS_ALLOCATION = 3;
 
+    // Predicate for whether the given logical address is remote device's one or not.
+    private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() {
+        @Override
+        public boolean apply(Integer address) {
+            return !isAllocatedLocalDeviceAddress(address);
+        }
+    };
+
+    // Predicate whether the given logical address is system audio's one or not
+    private final Predicate<Integer> mSystemAudioAddressPredicate = new Predicate<Integer>() {
+        @Override
+        public boolean apply(Integer address) {
+            return HdmiCec.getTypeFromAddress(address) == HdmiCec.ADDR_AUDIO_SYSTEM;
+        }
+    };
+
     // Handler instance to process synchronous I/O (mainly send) message.
     private Handler mIoHandler;
 
@@ -116,45 +148,13 @@
         mNativePtr = nativePtr;
     }
 
-    /**
-     * Perform initialization for each hosted device.
-     *
-     * @param deviceTypes array of device types
-     */
-    void initializeLocalDevices(int[] deviceTypes,
-            HdmiCecLocalDevice.AddressAllocationCallback callback) {
-        assertRunOnServiceThread();
-        for (int type : deviceTypes) {
-            HdmiCecLocalDevice device = HdmiCecLocalDevice.create(this, type, callback);
-            if (device == null) {
-                continue;
-            }
-            // TODO: Consider restoring the local device addresses from persistent storage
-            //       to allocate the same addresses again if possible.
-            device.setPreferredAddress(HdmiCec.ADDR_UNREGISTERED);
-            mLocalDevices.put(type, device);
-            device.init();
-        }
-    }
-
-    /**
-     * Interface to report allocated logical address.
-     */
-    interface AllocateLogicalAddressCallback {
-        /**
-         * Called when a new logical address is allocated.
-         *
-         * @param deviceType requested device type to allocate logical address
-         * @param logicalAddress allocated logical address. If it is
-         *                       {@link HdmiCec#ADDR_UNREGISTERED}, it means that
-         *                       it failed to allocate logical address for the given device type
-         */
-        void onAllocated(int deviceType, int logicalAddress);
+    void addLocalDevice(int deviceType, HdmiCecLocalDevice device) {
+        mLocalDevices.put(deviceType, device);
     }
 
     /**
      * Allocate a new logical address of the given device type. Allocated
-     * address will be reported through {@link AllocateLogicalAddressCallback}.
+     * address will be reported through {@link AllocateAddressCallback}.
      *
      * <p> Declared as package-private, accessed by {@link HdmiControlService} only.
      *
@@ -166,7 +166,7 @@
      * @param callback callback interface to report allocated logical address to caller
      */
     void allocateLogicalAddress(final int deviceType, final int preferredAddress,
-            final AllocateLogicalAddressCallback callback) {
+            final AllocateAddressCallback callback) {
         assertRunOnServiceThread();
 
         runOnIoThread(new Runnable() {
@@ -178,7 +178,7 @@
     }
 
     private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress,
-            final AllocateLogicalAddressCallback callback) {
+            final AllocateAddressCallback callback) {
         assertRunOnIoThread();
         int startAddress = preferredAddress;
         // If preferred address is "unregistered", start address will be the smallest
@@ -275,10 +275,23 @@
      * Return a list of all {@link HdmiCecDeviceInfo}.
      *
      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+     *
+     * @param includeLocalDevice whether to add local device or not
      */
-    List<HdmiCecDeviceInfo> getDeviceInfoList() {
+    List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
         assertRunOnServiceThread();
-        return sparseArrayToList(mDeviceInfos);
+        if (includeLocalDevice) {
+            return sparseArrayToList(mDeviceInfos);
+        } else {
+            ArrayList<HdmiCecDeviceInfo> infoList = new ArrayList<>();
+            for (int i = 0; i < mDeviceInfos.size(); ++i) {
+                HdmiCecDeviceInfo info = mDeviceInfos.valueAt(i);
+                if (mRemoteDeviceAddressPredicate.apply(info.getLogicalAddress())) {
+                    infoList.add(info);
+                }
+            }
+            return infoList;
+        }
     }
 
     /**
@@ -374,24 +387,55 @@
     }
 
     /**
+     * Pass a option to CEC HAL.
+     *
+     * @param flag a key of option. For more details, look at
+     *        {@link HdmiConstants#FLAG_HDMI_OPTION_WAKEUP} to
+     *        {@link HdmiConstants#FLAG_HDMI_OPTION_SYSTEM_CEC_CONTROL}
+     * @param value a value of option. Actual value varies flag. For more
+     *        details, look at description of flags
+     */
+    void setOption(int flag, int value) {
+        assertRunOnServiceThread();
+        nativeSetOption(mNativePtr, flag, value);
+    }
+
+    /**
+     * Configure ARC circuit in the hardware logic to start or stop the feature.
+     *
+     * @param enabled whether to enable/disable ARC
+     */
+    void setAudioReturnChannel(boolean enabled) {
+        assertRunOnServiceThread();
+        nativeSetAudioReturnChannel(mNativePtr, enabled);
+    }
+
+    /**
+     * Return the connection status of the specified port
+     *
+     * @param port port number to check connection status
+     * @return true if connected; otherwise, return false
+     */
+    boolean isConnected(int port) {
+        assertRunOnServiceThread();
+        return nativeIsConnected(mNativePtr, port);
+    }
+
+    /**
      * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
      * devices.
      *
      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
      *
      * @param callback an interface used to get a list of all remote devices' address
+     * @param pickStrategy strategy how to pick polling candidates
      * @param retryCount the number of retry used to send polling message to remote devices
      */
-    void pollDevices(DevicePollingCallback callback, int retryCount) {
+    void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) {
         assertRunOnServiceThread();
-        // Extract polling candidates. No need to poll against local devices.
-        ArrayList<Integer> pollingCandidates = new ArrayList<>();
-        for (int i = HdmiCec.ADDR_SPECIFIC_USE; i >= HdmiCec.ADDR_TV; --i) {
-            if (!isAllocatedLocalDeviceAddress(i)) {
-                pollingCandidates.add(i);
-            }
-        }
 
+        // Extract polling candidates. No need to poll against local devices.
+        List<Integer> pollingCandidates = pickPollCandidates(pickStrategy);
         runDevicePolling(pollingCandidates, retryCount, callback);
     }
 
@@ -405,6 +449,41 @@
         return sparseArrayToList(mLocalDevices);
     }
 
+    private List<Integer> pickPollCandidates(int pickStrategy) {
+        int strategy = pickStrategy & HdmiControlService.POLL_STRATEGY_MASK;
+        Predicate<Integer> pickPredicate = null;
+        switch (strategy) {
+            case HdmiControlService.POLL_STRATEGY_SYSTEM_AUDIO:
+                pickPredicate = mSystemAudioAddressPredicate;
+                break;
+            case HdmiControlService.POLL_STRATEGY_REMOTES_DEVICES:
+            default:  // The default is POLL_STRATEGY_REMOTES_DEVICES.
+                pickPredicate = mRemoteDeviceAddressPredicate;
+                break;
+        }
+
+        int iterationStrategy = pickStrategy & HdmiControlService.POLL_ITERATION_STRATEGY_MASK;
+        ArrayList<Integer> pollingCandidates = new ArrayList<>();
+        switch (iterationStrategy) {
+            case HdmiControlService.POLL_ITERATION_IN_ORDER:
+                for (int i = HdmiCec.ADDR_TV; i <= HdmiCec.ADDR_SPECIFIC_USE; ++i) {
+                    if (pickPredicate.apply(i)) {
+                        pollingCandidates.add(i);
+                    }
+                }
+                break;
+            case HdmiControlService.POLL_ITERATION_REVERSE_ORDER:
+            default:  // The default is reverse order.
+                for (int i = HdmiCec.ADDR_SPECIFIC_USE; i >= HdmiCec.ADDR_TV; --i) {
+                    if (pickPredicate.apply(i)) {
+                        pollingCandidates.add(i);
+                    }
+                }
+                break;
+        }
+        return pollingCandidates;
+    }
+
     private static <T> List<T> sparseArrayToList(SparseArray<T> array) {
         ArrayList<T> list = new ArrayList<>();
         for (int i = 0; i < array.size(); ++i) {
@@ -505,7 +584,7 @@
             // Reply <Feature Abort> to initiator (source) for all requests.
             HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
                     sourceAddress, message.getSource(), message.getOpcode(),
-                    HdmiCecMessageBuilder.ABORT_REFUSED);
+                    HdmiConstants.ABORT_REFUSED);
             sendCommand(cecMessage, null);
         }
     }
@@ -562,4 +641,8 @@
     private static native int nativeGetPhysicalAddress(long controllerPtr);
     private static native int nativeGetVersion(long controllerPtr);
     private static native int nativeGetVendorId(long controllerPtr);
+    private static native void nativeSetOption(long controllerPtr, int flag, int value);
+    private static native void nativeSetAudioReturnChannel(long controllerPtr, boolean flag);
+    private static native boolean nativeIsConnected(long controllerPtr, int port);
+
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecKeycodeTranslator.java b/services/core/java/com/android/server/hdmi/HdmiCecKeycodeTranslator.java
new file mode 100644
index 0000000..ebb6f50
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecKeycodeTranslator.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.view.KeyEvent;
+
+/**
+ * Helper class to translate android keycode to hdmi cec keycode and vice versa.
+ */
+public class HdmiCecKeycodeTranslator {
+    public static final int UNSUPPORTED_KEYCODE = -1;
+    public static final int NO_PARAM = -1;
+
+    // =========================================================================
+    // Hdmi CEC keycodes
+    public static final int CEC_KEYCODE_SELECT = 0x00;
+    public static final int CEC_KEYCODE_UP = 0x01;
+    public static final int CEC_KEYCODE_DOWN = 0x02;
+    public static final int CEC_KEYCODE_LEFT = 0x03;
+    public static final int CEC_KEYCODE_RIGHT = 0x04;
+    public static final int CEC_KEYCODE_RIGHT_UP = 0x05;
+    public static final int CEC_KEYCODE_RIGHT_DOWN = 0x06;
+    public static final int CEC_KEYCODE_LEFT_UP = 0x07;
+    public static final int CEC_KEYCODE_LEFT_DOWN = 0x08;
+    public static final int CEC_KEYCODE_ROOT_MENU = 0x09;
+    public static final int CEC_KEYCODE_SETUP_MENU = 0x0A;
+    public static final int CEC_KEYCODE_CONTENTS_MENU = 0x0B;
+    public static final int CEC_KEYCODE_FAVORITE_MENU = 0x0C;
+    public static final int CEC_KEYCODE_EXIT = 0x0D;
+    // RESERVED = 0x0E - 0x0F
+    public static final int CEC_KEYCODE_MEDIA_TOP_MENU = 0x10;
+    public static final int CEC_KEYCODE_MEDIA_CONTEXT_SENSITIVE_MENU = 0x11;
+    // RESERVED = 0x12 – 0x1C
+    public static final int CEC_KEYCODE_NUMBER_ENTRY_MODE = 0x1D;
+    public static final int CEC_KEYCODE_NUMBER_11 = 0x1E;
+    public static final int CEC_KEYCODE_NUMBER_12 = 0x1F;
+    public static final int CEC_KEYCODE_NUMBER_0_OR_NUMBER_10 = 0x20;
+    public static final int CEC_KEYCODE_NUMBERS_1 = 0x21;
+    public static final int CEC_KEYCODE_NUMBERS_2 = 0x22;
+    public static final int CEC_KEYCODE_NUMBERS_3 = 0x23;
+    public static final int CEC_KEYCODE_NUMBERS_4 = 0x24;
+    public static final int CEC_KEYCODE_NUMBERS_5 = 0x25;
+    public static final int CEC_KEYCODE_NUMBERS_6 = 0x26;
+    public static final int CEC_KEYCODE_NUMBERS_7 = 0x27;
+    public static final int CEC_KEYCODE_NUMBERS_8 = 0x28;
+    public static final int CEC_KEYCODE_NUMBERS_9 = 0x29;
+    public static final int CEC_KEYCODE_DOT = 0x2A;
+    public static final int CEC_KEYCODE_ENTER = 0x2B;
+    public static final int CEC_KEYCODE_CLEAR = 0x2C;
+    // RESERVED = 0x2D - 0x2E
+    public static final int CEC_KEYCODE_NEXT_FAVORITE = 0x2F;
+    public static final int CEC_KEYCODE_CHANNEL_UP = 0x30;
+    public static final int CEC_KEYCODE_CHANNEL_DOWN = 0x31;
+    public static final int CEC_KEYCODE_PREVIOUS_CHANNEL = 0x32;
+    public static final int CEC_KEYCODE_SOUND_SELECT = 0x33;
+    public static final int CEC_KEYCODE_INPUT_SELECT = 0x34;
+    public static final int CEC_KEYCODE_DISPLAY_INFORMATION = 0x35;
+    public static final int CEC_KEYCODE_HELP = 0x36;
+    public static final int CEC_KEYCODE_PAGE_UP = 0x37;
+    public static final int CEC_KEYCODE_PAGE_DOWN = 0x38;
+    // RESERVED = 0x39 - 0x3F
+    public static final int CEC_KEYCODE_POWER = 0x40;
+    public static final int CEC_KEYCODE_VOLUME_UP = 0x41;
+    public static final int CEC_KEYCODE_VOLUME_DOWN = 0x42;
+    public static final int CEC_KEYCODE_MUTE = 0x43;
+    public static final int CEC_KEYCODE_PLAY = 0x44;
+    public static final int CEC_KEYCODE_STOP = 0x45;
+    public static final int CEC_KEYCODE_PAUSE = 0x46;
+    public static final int CEC_KEYCODE_RECORD = 0x47;
+    public static final int CEC_KEYCODE_REWIND = 0x48;
+    public static final int CEC_KEYCODE_FAST_FORWARD = 0x49;
+    public static final int CEC_KEYCODE_EJECT = 0x4A;
+    public static final int CEC_KEYCODE_FORWARD = 0x4B;
+    public static final int CEC_KEYCODE_BACKWARD = 0x4C;
+    public static final int CEC_KEYCODE_STOP_RECORD = 0x4D;
+    public static final int CEC_KEYCODE_PAUSE_RECORD = 0x4E;
+    public static final int CEC_KEYCODE_RESERVED = 0x4F;
+    public static final int CEC_KEYCODE_ANGLE = 0x50;
+    public static final int CEC_KEYCODE_SUB_PICTURE = 0x51;
+    public static final int CEC_KEYCODE_VIDEO_ON_DEMAND = 0x52;
+    public static final int CEC_KEYCODE_ELECTRONIC_PROGRAM_GUIDE = 0x53;
+    public static final int CEC_KEYCODE_TIMER_PROGRAMMING = 0x54;
+    public static final int CEC_KEYCODE_INITIAL_CONFIGURATION = 0x55;
+    public static final int CEC_KEYCODE_SELECT_BROADCAST_TYPE = 0x56;
+    public static final int CEC_KEYCODE_SELECT_SOUND_PRESENTATION = 0x57;
+    // RESERVED = 0x58-0x5F
+    public static final int CEC_KEYCODE_PLAY_FUNCTION = 0x60;
+    public static final int CEC_KEYCODE_PAUSE_PLAY_FUNCTION = 0x61;
+    public static final int CEC_KEYCODE_RECORD_FUNCTION = 0x62;
+    public static final int CEC_KEYCODE_PAUSE_RECORD_FUNCTION = 0x63;
+    public static final int CEC_KEYCODE_STOP_FUNCTION = 0x64;
+    public static final int CEC_KEYCODE_MUTE_FUNCTION = 0x65;
+    public static final int CEC_KEYCODE_RESTORE_VOLUME_FUNCTION = 0x66;
+    public static final int CEC_KEYCODE_TUNE_FUNCTION = 0x67;
+    public static final int CEC_KEYCODE_SELECT_MEDIA_FUNCTION = 0x68;
+    public static final int CEC_KEYCODE_SELECT_AV_INPUT_FUNCTION = 0x69;
+    public static final int CEC_KEYCODE_SELECT_AUDIO_INPUT_FUNCTION = 0x6A;
+    public static final int CEC_KEYCODE_POWER_TOGGLE_FUNCTION = 0x6B;
+    public static final int CEC_KEYCODE_POWER_OFF_FUNCTION = 0x6C;
+    public static final int CEC_KEYCODE_POWER_ON_FUNCTION = 0x6D;
+    // RESERVED = 0x6E-0x70
+    public static final int CEC_KEYCODE_F1_BLUE = 0x71;
+    public static final int CEC_KEYCODE_F2_RED = 0x72;
+    public static final int CEC_KEYCODE_F3_GREEN = 0x73;
+    public static final int CEC_KEYCODE_F4_YELLOW = 0x74;
+    public static final int CEC_KEYCODE_F5 = 0x75;
+    public static final int CEC_KEYCODE_DATA = 0x76;
+    // RESERVED = 0x77-0xFF
+
+    // =========================================================================
+    // UI Broadcast Type
+    public static final int UI_BROADCAST_TOGGLE_ALL = 0x00;
+    public static final int UI_BROADCAST_TOGGLE_ANALOGUE_DIGITAL = 0x01;
+    public static final int UI_BROADCAST_ANALOGUE = 0x10;
+    public static final int UI_BROADCAST_ANALOGUE_TERRESTRIAL = 0x20;
+    public static final int UI_BROADCAST_ANALOGUE_CABLE = 0x30;
+    public static final int UI_BROADCAST_ANALOGUE_SATELLITE = 0x40;
+    public static final int UI_BROADCAST_DIGITAL = 0x50;
+    public static final int UI_BROADCAST_DIGITAL_TERRESTRIAL = 0x60;
+    public static final int UI_BROADCAST_DIGITAL_CABLE = 0x70;
+    public static final int UI_BROADCAST_DIGITAL_SATELLITE = 0x80;
+    public static final int UI_BROADCAST_DIGITAL_COMMNICATIONS_SATELLITE = 0x90;
+    public static final int UI_BROADCAST_DIGITAL_COMMNICATIONS_SATELLITE_2 = 0x91;
+    public static final int UI_BROADCAST_IP = 0xA0;
+
+    // =========================================================================
+    // UI Sound Presentation Control
+    public static final int UI_SOUND_PRESENTATION_SOUND_MIX_DUAL_MONO = 0x20;
+    public static final int UI_SOUND_PRESENTATION_SOUND_MIX_KARAOKE = 0x30;
+    public static final int UI_SOUND_PRESENTATION_SELECT_AUDIO_DOWN_MIX = 0x80;
+    public static final int UI_SOUND_PRESENTATION_SELECT_AUDIO_AUTO_REVERBERATION = 0x90;
+    public static final int UI_SOUND_PRESENTATION_SELECT_AUDIO_AUTO_EQUALIZER = 0xA0;
+    public static final int UI_SOUND_PRESENTATION_BASS_STEP_PLUS = 0xB1;
+    public static final int UI_SOUND_PRESENTATION_BASS_NEUTRAL = 0xB2;
+    public static final int UI_SOUND_PRESENTATION_BASS_STEP_MINUS = 0xB3;
+    public static final int UI_SOUND_PRESENTATION_TREBLE_STEP_PLUS = 0xC1;
+    public static final int UI_SOUND_PRESENTATION_TREBLE_NEUTRAL = 0xC2;
+    public static final int UI_SOUND_PRESENTATION_TREBLE_STEP_MINUS = 0xC3;
+
+    private HdmiCecKeycodeTranslator() {
+    }
+
+    /**
+     * A mapping between andorid and cec keycode.
+     *
+     * <p>Normal implementation of this looks like
+     * <pre>
+     *    new KeycodeEntry(KeyEvent.KEYCODE_DPAD_CENTER, CEC_KEYCODE_SELECT);
+     * </pre>
+     * <p>However, some keys in CEC requires additional parameter.
+     * In order to use parameterized cec key, add unique android keycode (existing or custom)
+     * corresponding to a pair of cec keycode and and its param.
+     * <pre>
+     *    new KeycodeEntry(CUSTOME_ANDORID_KEY_1, CEC_KEYCODE_SELECT_BROADCAST_TYPE,
+     *        UI_BROADCAST_TOGGLE_ALL);
+     *    new KeycodeEntry(CUSTOME_ANDORID_KEY_2, CEC_KEYCODE_SELECT_BROADCAST_TYPE,
+     *        UI_BROADCAST_ANALOGUE);
+     * </pre>
+     */
+    private static class KeycodeEntry {
+        private final int mAndroidKeycode;
+        private final int mCecKeycode;
+        private final int mParam;
+
+        private KeycodeEntry(int androidKeycode, int cecKeycode, int param) {
+            this.mAndroidKeycode = androidKeycode;
+            this.mCecKeycode = cecKeycode;
+            this.mParam = param;
+        }
+
+        private KeycodeEntry(int androidKeycode, int cecKeycode) {
+            this(androidKeycode, cecKeycode, NO_PARAM);
+        }
+
+        private byte[] toCecKeycodeIfMatched(int androidKeycode) {
+            if (mAndroidKeycode == androidKeycode) {
+                if (mParam == NO_PARAM) {
+                    return new byte[] {
+                        (byte) (mCecKeycode & 0xFF)
+                    };
+                } else {
+                    return new byte[] {
+                        (byte) (mCecKeycode & 0xFF),
+                        (byte) (mParam & 0xFF)
+                    };
+                }
+            } else {
+                return null;
+            }
+        }
+
+        private int toAndroidKeycodeIfMatched(int cecKeycode, int param) {
+            if (cecKeycode == mCecKeycode && mParam == param) {
+                return mAndroidKeycode;
+            } else {
+                return UNSUPPORTED_KEYCODE;
+            }
+        }
+    }
+
+    // Keycode entry container for all mappings.
+    // Note that order of entry is the same as above cec keycode definition.
+    private static final KeycodeEntry[] KEYCODE_ENTRIES = new KeycodeEntry[] {
+            new KeycodeEntry(KeyEvent.KEYCODE_DPAD_CENTER, CEC_KEYCODE_SELECT),
+            new KeycodeEntry(KeyEvent.KEYCODE_DPAD_UP, CEC_KEYCODE_UP),
+            new KeycodeEntry(KeyEvent.KEYCODE_DPAD_DOWN, CEC_KEYCODE_DOWN),
+            new KeycodeEntry(KeyEvent.KEYCODE_DPAD_LEFT, CEC_KEYCODE_LEFT),
+            new KeycodeEntry(KeyEvent.KEYCODE_DPAD_RIGHT, CEC_KEYCODE_RIGHT),
+            // No Android keycode defined for CEC_KEYCODE_RIGHT_UP
+            // No Android keycode defined for CEC_KEYCODE_RIGHT_DOWN
+            // No Android keycode defined for CEC_KEYCODE_LEFT_UP
+            // No Android keycode defined for CEC_KEYCODE_LEFT_DOWN
+            new KeycodeEntry(KeyEvent.KEYCODE_HOME, CEC_KEYCODE_ROOT_MENU),
+            new KeycodeEntry(KeyEvent.KEYCODE_SETTINGS, CEC_KEYCODE_SETUP_MENU),
+            new KeycodeEntry(KeyEvent.KEYCODE_MENU, CEC_KEYCODE_CONTENTS_MENU),
+            // No Android keycode defined for CEC_KEYCODE_FAVORITE_MENU
+            new KeycodeEntry(KeyEvent.KEYCODE_BACK, CEC_KEYCODE_EXIT),
+            // RESERVED
+            new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_TOP_MENU, CEC_KEYCODE_MEDIA_TOP_MENU),
+            // No Android keycode defined for CEC_KEYCODE_MEDIA_CONTEXT_SENSITIVE_MENU
+            // RESERVED
+            // No Android keycode defined for CEC_KEYCODE_NUMBER_ENTRY_MODE
+            new KeycodeEntry(KeyEvent.KEYCODE_11, CEC_KEYCODE_NUMBER_11),
+            new KeycodeEntry(KeyEvent.KEYCODE_12, CEC_KEYCODE_NUMBER_12),
+            new KeycodeEntry(KeyEvent.KEYCODE_0, CEC_KEYCODE_NUMBER_0_OR_NUMBER_10),
+            new KeycodeEntry(KeyEvent.KEYCODE_1, CEC_KEYCODE_NUMBERS_1),
+            new KeycodeEntry(KeyEvent.KEYCODE_2, CEC_KEYCODE_NUMBERS_2),
+            new KeycodeEntry(KeyEvent.KEYCODE_3, CEC_KEYCODE_NUMBERS_3),
+            new KeycodeEntry(KeyEvent.KEYCODE_4, CEC_KEYCODE_NUMBERS_4),
+            new KeycodeEntry(KeyEvent.KEYCODE_5, CEC_KEYCODE_NUMBERS_5),
+            new KeycodeEntry(KeyEvent.KEYCODE_6, CEC_KEYCODE_NUMBERS_6),
+            new KeycodeEntry(KeyEvent.KEYCODE_7, CEC_KEYCODE_NUMBERS_7),
+            new KeycodeEntry(KeyEvent.KEYCODE_8, CEC_KEYCODE_NUMBERS_8),
+            new KeycodeEntry(KeyEvent.KEYCODE_9, CEC_KEYCODE_NUMBERS_9),
+            new KeycodeEntry(KeyEvent.KEYCODE_PERIOD, CEC_KEYCODE_DOT),
+            new KeycodeEntry(KeyEvent.KEYCODE_NUMPAD_ENTER, CEC_KEYCODE_ENTER),
+            new KeycodeEntry(KeyEvent.KEYCODE_CLEAR, CEC_KEYCODE_CLEAR),
+            // RESERVED
+            // No Android keycode defined for CEC_KEYCODE_NEXT_FAVORITE
+            new KeycodeEntry(KeyEvent.KEYCODE_CHANNEL_UP, CEC_KEYCODE_CHANNEL_UP),
+            new KeycodeEntry(KeyEvent.KEYCODE_CHANNEL_DOWN, CEC_KEYCODE_CHANNEL_DOWN),
+            new KeycodeEntry(KeyEvent.KEYCODE_LAST_CHANNEL, CEC_KEYCODE_PREVIOUS_CHANNEL),
+            // No Android keycode defined for CEC_KEYCODE_SOUND_SELECT
+            new KeycodeEntry(KeyEvent.KEYCODE_TV_INPUT, CEC_KEYCODE_INPUT_SELECT),
+            new KeycodeEntry(KeyEvent.KEYCODE_INFO, CEC_KEYCODE_DISPLAY_INFORMATION),
+            // No Android keycode defined for CEC_KEYCODE_HELP
+            new KeycodeEntry(KeyEvent.KEYCODE_PAGE_UP, CEC_KEYCODE_PAGE_UP),
+            new KeycodeEntry(KeyEvent.KEYCODE_PAGE_DOWN, CEC_KEYCODE_PAGE_DOWN),
+            // RESERVED
+            new KeycodeEntry(KeyEvent.KEYCODE_POWER, CEC_KEYCODE_POWER),
+            new KeycodeEntry(KeyEvent.KEYCODE_VOLUME_UP, CEC_KEYCODE_VOLUME_UP),
+            new KeycodeEntry(KeyEvent.KEYCODE_VOLUME_DOWN, CEC_KEYCODE_VOLUME_DOWN),
+            new KeycodeEntry(KeyEvent.KEYCODE_VOLUME_MUTE, CEC_KEYCODE_MUTE),
+            new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_PLAY, CEC_KEYCODE_PLAY),
+            new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_STOP, CEC_KEYCODE_STOP),
+            new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_PAUSE, CEC_KEYCODE_PAUSE),
+            new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_RECORD, CEC_KEYCODE_RECORD),
+            new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_REWIND, CEC_KEYCODE_REWIND),
+            new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, CEC_KEYCODE_FAST_FORWARD),
+            new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_EJECT, CEC_KEYCODE_EJECT),
+            new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_NEXT, CEC_KEYCODE_FORWARD),
+            new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_PREVIOUS, CEC_KEYCODE_BACKWARD),
+            // No Android keycode defined for CEC_KEYCODE_STOP_RECORD
+            // No Android keycode defined for CEC_KEYCODE_PAUSE_RECORD
+            // No Android keycode defined for CEC_KEYCODE_RESERVED
+            // No Android keycode defined for CEC_KEYCODE_ANGLE
+            // No Android keycode defined for CEC_KEYCODE_SUB_PICTURE
+            // No Android keycode defined for CEC_KEYCODE_VIDEO_ON_DEMAND
+            new KeycodeEntry(KeyEvent.KEYCODE_GUIDE, CEC_KEYCODE_ELECTRONIC_PROGRAM_GUIDE),
+            // No Android keycode defined for CEC_KEYCODE_TIMER_PROGRAMMING
+            // No Android keycode defined for CEC_KEYCODE_INITIAL_CONFIGURATION
+            // No Android keycode defined for CEC_KEYCODE_SELECT_BROADCAST_TYPE
+            // No Android keycode defined for CEC_KEYCODE_SELECT_SOUND_PRESENTATION
+            // RESERVED
+            // The following deterministic key definitions do not need key mapping
+            // since they are supposed to be generated programmatically only.
+            // No Android keycode defined for CEC_KEYCODE_PLAY_FUNCTION
+            // No Android keycode defined for CEC_KEYCODE_PAUSE_PLAY_FUNCTION
+            // No Android keycode defined for CEC_KEYCODE_RECORD_FUNCTION
+            // No Android keycode defined for CEC_KEYCODE_PAUSE_RECORD_FUNCTION
+            // No Android keycode defined for CEC_KEYCODE_STOP_FUNCTION
+            // No Android keycode defined for CEC_KEYCODE_MUTE_FUNCTION
+            // No Android keycode defined for CEC_KEYCODE_RESTORE_VOLUME_FUNCTION
+            // No Android keycode defined for CEC_KEYCODE_TUNE_FUNCTION
+            // No Android keycode defined for CEC_KEYCODE_SELECT_MEDIA_FUNCTION
+            // No Android keycode defined for CEC_KEYCODE_SELECT_AV_INPUT_FUNCTION
+            // No Android keycode defined for CEC_KEYCODE_SELECT_AUDIO_INPUT_FUNCTION
+            // No Android keycode defined for CEC_KEYCODE_POWER_TOGGLE_FUNCTION
+            // No Android keycode defined for CEC_KEYCODE_POWER_OFF_FUNCTION
+            // No Android keycode defined for CEC_KEYCODE_POWER_ON_FUNCTION
+            // RESERVED
+            new KeycodeEntry(KeyEvent.KEYCODE_PROG_BLUE, CEC_KEYCODE_F1_BLUE),
+            new KeycodeEntry(KeyEvent.KEYCODE_PROG_RED, CEC_KEYCODE_F2_RED),
+            new KeycodeEntry(KeyEvent.KEYCODE_PROG_GREEN, CEC_KEYCODE_F3_GREEN),
+            new KeycodeEntry(KeyEvent.KEYCODE_PROG_YELLOW, CEC_KEYCODE_F4_YELLOW),
+            new KeycodeEntry(KeyEvent.KEYCODE_F5, CEC_KEYCODE_F5),
+            new KeycodeEntry(KeyEvent.KEYCODE_TV_DATA_SERVICE, CEC_KEYCODE_DATA),
+            // RESERVED
+            // Add a new key mapping here if new keycode is introduced.
+    };
+
+    /**
+     * Translate Android keycode to Hdmi Cec keycode.
+     *
+     * @param keycode Android keycode. For details, refer {@link KeyEvent}
+     * @return array of byte which contains cec keycode and param if it has;
+     *         return null if failed to find matched cec keycode
+     */
+    static byte[] androidKeyToCecKey(int keycode) {
+        for (int i = 0; i < KEYCODE_ENTRIES.length; ++i) {
+            byte[] cecKeycode = KEYCODE_ENTRIES[i].toCecKeycodeIfMatched(keycode);
+            if (cecKeycode != null) {
+                return cecKeycode;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Translate Hdmi CEC keycode to Android keycode.
+     *
+     * @param keycode Cec keycode. If has no param, put {@link #NO_PARAM}
+     * @return cec keycode corresponding to the given android keycode.
+     *         If finds no matched keycode, return {@link #UNSUPPORTED_KEYCODE}
+     */
+    static int cecKeyToAndroidKey(int keycode, int param) {
+        for (int i = 0; i < KEYCODE_ENTRIES.length; ++i) {
+            int androidKey = KEYCODE_ENTRIES[i].toAndroidKeycodeIfMatched(keycode, param);
+            if (androidKey != UNSUPPORTED_KEYCODE) {
+                return androidKey;
+            }
+        }
+        return UNSUPPORTED_KEYCODE;
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index aac2a15..6697a53 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -16,95 +16,132 @@
 
 package com.android.server.hdmi;
 
-import com.android.server.hdmi.HdmiCecController.AllocateLogicalAddressCallback;
-
 import android.hardware.hdmi.HdmiCec;
 import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.util.Slog;
 
 /**
  * Class that models a logical CEC device hosted in this system. Handles initialization,
  * CEC commands that call for actions customized per device type.
  */
 abstract class HdmiCecLocalDevice {
+    private static final String TAG = "HdmiCecLocalDevice";
 
-    protected final HdmiCecController mController;
+    protected final HdmiControlService mService;
     protected final int mDeviceType;
-    protected final AddressAllocationCallback mAllocationCallback;
     protected int mAddress;
     protected int mPreferredAddress;
     protected HdmiCecDeviceInfo mDeviceInfo;
 
-    /**
-     * Callback interface to notify newly allocated logical address of the given
-     * local device.
-     */
-    interface AddressAllocationCallback {
-        /**
-         * Called when a logical address of the given device is allocated.
-         *
-         * @param deviceType original device type
-         * @param logicalAddress newly allocated logical address
-         */
-        void onAddressAllocated(int deviceType, int logicalAddress);
-    }
-
-    protected HdmiCecLocalDevice(HdmiCecController controller, int deviceType,
-            AddressAllocationCallback callback) {
-        mController = controller;
+    protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) {
+        mService = service;
         mDeviceType = deviceType;
-        mAllocationCallback = callback;
         mAddress = HdmiCec.ADDR_UNREGISTERED;
     }
 
     // Factory method that returns HdmiCecLocalDevice of corresponding type.
-    static HdmiCecLocalDevice create(HdmiCecController controller, int deviceType,
-            AddressAllocationCallback callback) {
+    static HdmiCecLocalDevice create(HdmiControlService service, int deviceType) {
         switch (deviceType) {
         case HdmiCec.DEVICE_TV:
-            return new HdmiCecLocalDeviceTv(controller, callback);
+            return new HdmiCecLocalDeviceTv(service);
         case HdmiCec.DEVICE_PLAYBACK:
-            return new HdmiCecLocalDevicePlayback(controller, callback);
+            return new HdmiCecLocalDevicePlayback(service);
         default:
             return null;
         }
     }
 
-    abstract void init();
+    void init() {
+        mPreferredAddress = HdmiCec.ADDR_UNREGISTERED;
+        // TODO: load preferred address from permanent storage.
+    }
 
     /**
-     * Called when a logical address of the local device is allocated.
-     * Note that internal variables are updated before it's called.
+     * Called once a logical address of the local device is allocated.
      */
     protected abstract void onAddressAllocated(int logicalAddress);
 
-    protected void allocateAddress(int type) {
-        mController.allocateLogicalAddress(type, mPreferredAddress,
-                new AllocateLogicalAddressCallback() {
-            @Override
-            public void onAllocated(int deviceType, int logicalAddress) {
-                mAddress = mPreferredAddress = logicalAddress;
-
-                // Create and set device info.
-                HdmiCecDeviceInfo deviceInfo = createDeviceInfo(mAddress, deviceType);
-                setDeviceInfo(deviceInfo);
-                mController.addDeviceInfo(deviceInfo);
-
-                mController.addLogicalAddress(logicalAddress);
-                onAddressAllocated(logicalAddress);
-                if (mAllocationCallback != null) {
-                    mAllocationCallback.onAddressAllocated(deviceType, logicalAddress);
-                }
-            }
-        });
+    /**
+     * Dispatch incoming message.
+     *
+     * @param message incoming message
+     * @return true if consumed a message; otherwise, return false.
+     */
+    final boolean dispatchMessage(HdmiCecMessage message) {
+        int dest = message.getDestination();
+        if (dest != mAddress && dest != HdmiCec.ADDR_BROADCAST) {
+            return false;
+        }
+        return onMessage(message);
     }
 
-    private final HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) {
-        int vendorId = mController.getVendorId();
-        int physicalAddress = mController.getPhysicalAddress();
-        // TODO: get device name read from system configuration.
-        String displayName = HdmiCec.getDefaultDeviceName(logicalAddress);
-        return new HdmiCecDeviceInfo(logicalAddress,
-                physicalAddress, deviceType, vendorId, displayName);
+    protected boolean onMessage(HdmiCecMessage message) {
+        switch (message.getOpcode()) {
+            case HdmiCec.MESSAGE_GET_MENU_LANGUAGE:
+                return handleGetMenuLanguage(message);
+            case HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS:
+                return handleGivePhysicalAddress();
+            case HdmiCec.MESSAGE_GIVE_OSD_NAME:
+                return handleGiveOsdName(message);
+            case HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID:
+                return handleGiveDeviceVendorId();
+            case HdmiCec.MESSAGE_GET_CEC_VERSION:
+                return handleGetCecVersion(message);
+            default:
+                return false;
+        }
+    }
+
+    protected boolean handleGivePhysicalAddress() {
+        int physicalAddress = mService.getPhysicalAddress();
+        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                mAddress, physicalAddress, mDeviceType);
+        mService.sendCecCommand(cecMessage);
+        return true;
+    }
+
+    protected boolean handleGiveDeviceVendorId() {
+        int vendorId = mService.getVendorId();
+        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
+                mAddress, vendorId);
+        mService.sendCecCommand(cecMessage);
+        return true;
+    }
+
+    protected boolean handleGetCecVersion(HdmiCecMessage message) {
+        int version = mService.getCecVersion();
+        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
+                message.getSource(), version);
+        mService.sendCecCommand(cecMessage);
+        return true;
+    }
+
+    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
+        Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
+        mService.sendCecCommand(
+                HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress,
+                        message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE,
+                        HdmiConstants.ABORT_UNRECOGNIZED_MODE));
+        return true;
+    }
+
+    protected boolean handleGiveOsdName(HdmiCecMessage message) {
+        // Note that since this method is called after logical address allocation is done,
+        // mDeviceInfo should not be null.
+        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand(
+                mAddress, message.getSource(), mDeviceInfo.getDisplayName());
+        if (cecMessage != null) {
+            mService.sendCecCommand(cecMessage);
+        } else {
+            Slog.w(TAG, "Failed to build <Get Osd Name>:" + mDeviceInfo.getDisplayName());
+        }
+        return true;
+    }
+
+    final void handleAddressAllocated(int logicalAddress) {
+        mAddress = mPreferredAddress = logicalAddress;
+        onAddressAllocated(logicalAddress);
     }
 
     HdmiCecDeviceInfo getDeviceInfo() {
@@ -128,4 +165,8 @@
     void setPreferredAddress(int addr) {
         mPreferredAddress = addr;
     }
+
+    int getPreferredAddress() {
+        return mPreferredAddress;
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 3347725..d79e283 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -23,18 +23,13 @@
  */
 final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
 
-    HdmiCecLocalDevicePlayback(HdmiCecController controller, AddressAllocationCallback callback) {
-        super(controller, HdmiCec.DEVICE_PLAYBACK, callback);
-    }
-
-    @Override
-    void init() {
-        allocateAddress(mDeviceType);
+    HdmiCecLocalDevicePlayback(HdmiControlService service) {
+        super(service, HdmiCec.DEVICE_PLAYBACK);
     }
 
     @Override
     protected void onAddressAllocated(int logicalAddress) {
-        mController.sendCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
-                mAddress, mController.getPhysicalAddress(), mDeviceType));
+        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                mAddress, mService.getPhysicalAddress(), mDeviceType));
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 93761ab..8bd81ea 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -17,30 +17,69 @@
 package com.android.server.hdmi;
 
 import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.util.Slog;
+
+import java.util.Locale;
 
 /**
  * Represent a logical device of type TV residing in Android system.
  */
 final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
+    private static final String TAG = "HdmiCecLocalDeviceTv";
 
-    HdmiCecLocalDeviceTv(HdmiCecController controller, AddressAllocationCallback callback) {
-        super(controller, HdmiCec.DEVICE_TV, callback);
-    }
-
-    @Override
-    void init() {
-        allocateAddress(mDeviceType);
+    HdmiCecLocalDeviceTv(HdmiControlService service) {
+        super(service, HdmiCec.DEVICE_TV);
     }
 
     @Override
     protected void onAddressAllocated(int logicalAddress) {
         // TODO: vendor-specific initialization here.
 
-        mController.sendCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
-                mAddress, mController.getPhysicalAddress(), mDeviceType));
-        mController.sendCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
-                mAddress, mController.getVendorId()));
+        mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                mAddress, mService.getPhysicalAddress(), mDeviceType));
+        mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
+                mAddress, mService.getVendorId()));
 
+        mService.launchDeviceDiscovery(mAddress);
         // TODO: Start routing control action, device discovery action.
     }
+
+    @Override
+    protected boolean onMessage(HdmiCecMessage message) {
+        switch (message.getOpcode()) {
+            case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS:
+                return handleReportPhysicalAddress(message);
+            default:
+                return super.onMessage(message);
+        }
+    }
+
+    @Override
+    protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
+        HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
+                mAddress, Locale.getDefault().getISO3Language());
+        // TODO: figure out how to handle failed to get language code.
+        if (command != null) {
+            mService.sendCecCommand(command);
+        } else {
+            Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
+        }
+        return true;
+    }
+
+    private boolean handleReportPhysicalAddress(HdmiCecMessage message) {
+        // Ignore if [Device Discovery Action] is going on.
+        if (mService.hasAction(DeviceDiscoveryAction.class)) {
+            Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> "
+                    + "because Device Discovery Action is on-going:" + message);
+            return true;
+        }
+
+        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+        mService.addAndStartAction(new NewDeviceAction(mService,
+                mAddress, message.getSource(), physicalAddress));
+
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 9a76734..1fcb32f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -26,15 +26,6 @@
  * A helper class to build {@link HdmiCecMessage} from various cec commands.
  */
 public class HdmiCecMessageBuilder {
-    // TODO: move these values to HdmiCec.java once make it internal constant class.
-    // CEC's ABORT reason values.
-    static final int ABORT_UNRECOGNIZED_MODE = 0;
-    static final int ABORT_NOT_IN_CORRECT_MODE = 1;
-    static final int ABORT_CANNOT_PROVIDE_SOURCE = 2;
-    static final int ABORT_INVALID_OPERAND = 3;
-    static final int ABORT_REFUSED = 4;
-    static final int ABORT_UNABLE_TO_DETERMINE = 5;
-
     private static final int OSD_NAME_MAX_LENGTH = 13;
 
     private HdmiCecMessageBuilder() {}
@@ -290,6 +281,73 @@
     }
 
     /**
+     * Build &lt;System Audio Mode Request&gt; command.
+     *
+     * @param src source address of command
+     * @param avr destination address of command, it should be AVR
+     * @param avrPhysicalAddress physical address of AVR
+     * @param enableSystemAudio whether to enable System Audio Mode or not
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildSystemAudioModeRequest(int src, int avr, int avrPhysicalAddress,
+            boolean enableSystemAudio) {
+        if (enableSystemAudio) {
+            return buildCommand(src, avr, HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
+                    physicalAddressToParam(avrPhysicalAddress));
+        } else {
+            return buildCommand(src, avr, HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST);
+        }
+    }
+
+    /**
+     * Build &lt;Give Audio Status&gt; command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildGiveAudioStatus(int src, int dest) {
+        return buildCommand(src, dest, HdmiCec.MESSAGE_GIVE_AUDIO_STATUS);
+    }
+
+    /**
+     * Build &lt;User Control Pressed&gt; command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @param uiCommand keycode that user pressed
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildUserControlPressed(int src, int dest, int uiCommand) {
+        return buildUserControlPressed(src, dest, new byte[] { (byte) uiCommand });
+    }
+
+    /**
+     * Build &lt;User Control Pressed&gt; command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @param commandParam uiCommand and the additional parameter
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildUserControlPressed(int src, int dest, byte[] commandParam) {
+        return buildCommand(src, dest, HdmiCec.MESSAGE_USER_CONTROL_PRESSED, commandParam);
+    }
+
+    /**
+     * Build &lt;User Control Released&gt; command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildUserControlReleased(int src, int dest) {
+        return buildCommand(src, dest, HdmiCec.MESSAGE_USER_CONTROL_RELEASED);
+    }
+
+    /***** Please ADD new buildXXX() methods above. ******/
+
+    /**
      * Build a {@link HdmiCecMessage} without extra parameter.
      *
      * @param src source address of command
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageCache.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageCache.java
new file mode 100644
index 0000000..abda656
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageCache.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.util.FastImmutableArraySet;
+import android.util.SparseArray;
+
+/**
+ * Cache for incoming message. It caches {@link HdmiCecMessage} with source address and opcode
+ * as a key.
+ *
+ * <p>Note that whenever a device is removed it should call {@link #flushMessagesFrom(int)}
+ * to clean up messages come from the device.
+ */
+final class HdmiCecMessageCache {
+    private static final FastImmutableArraySet<Integer> CACHEABLE_OPCODES = new FastImmutableArraySet<>(
+            new Integer[] {
+                    HdmiCec.MESSAGE_SET_OSD_NAME,
+                    HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS,
+                    HdmiCec.MESSAGE_DEVICE_VENDOR_ID,
+                    HdmiCec.MESSAGE_CEC_VERSION,
+            });
+
+    // It's like [Source Logical Address, [Opcode, HdmiCecMessage]].
+    private final SparseArray<SparseArray<HdmiCecMessage>> mCache = new SparseArray<>();
+
+    HdmiCecMessageCache() {
+    }
+
+    /**
+     * Return a {@link HdmiCecMessage} corresponding to the given {@code address} and
+     * {@code opcode}.
+     *
+     * @param address a logical address of source device
+     * @param opcode opcode of message
+     * @return null if has no {@link HdmiCecMessage} matched to the given {@code address} and {code
+     *         opcode}
+     */
+    public HdmiCecMessage getMessage(int address, int opcode) {
+        SparseArray<HdmiCecMessage> messages = mCache.get(address);
+        if (messages == null) {
+            return null;
+        }
+
+        return messages.get(opcode);
+    }
+
+    /**
+     * Flush all {@link HdmiCecMessage}s sent from the given {@code address}.
+     *
+     * @param address a logical address of source device
+     */
+    public void flushMessagesFrom(int address) {
+        mCache.remove(address);
+    }
+
+    /**
+     * Flush all cached {@link HdmiCecMessage}s.
+     */
+    public void flushAll() {
+        mCache.clear();
+    }
+
+    /**
+     * Cache incoming {@link HdmiCecMessage}. If opcode of message is not listed on
+     * cacheable opcodes list, just ignore it.
+     *
+     * @param message a {@link HdmiCecMessage} to be cached
+     */
+    public void cacheMessage(HdmiCecMessage message) {
+        int opcode = message.getOpcode();
+        if (!isCacheable(opcode)) {
+            return;
+        }
+
+        int source = message.getSource();
+        SparseArray<HdmiCecMessage> messages = mCache.get(source);
+        if (messages == null) {
+            messages = new SparseArray<>();
+            mCache.put(source, messages);
+        }
+        messages.put(opcode, message);
+    }
+
+    private boolean isCacheable(int opcode) {
+        return CACHEABLE_OPCODES.contains(opcode);
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiConstants.java b/services/core/java/com/android/server/hdmi/HdmiConstants.java
new file mode 100644
index 0000000..54b5dcb
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiConstants.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+/**
+ * Defines constants related to HDMI-CEC protocol internal implementation.
+ * If a constant will be used in the public api, it should be located in
+ * {@link android.hardware.hdmi.HdmiCec}.
+ */
+final class HdmiConstants {
+
+    // Constants related to operands of HDMI CEC commands.
+    // Refer to CEC Table 29 in HDMI Spec v1.4b.
+    // [Abort Reason]
+    static final int ABORT_UNRECOGNIZED_MODE = 0;
+    static final int ABORT_NOT_IN_CORRECT_MODE = 1;
+    static final int ABORT_CANNOT_PROVIDE_SOURCE = 2;
+    static final int ABORT_INVALID_OPERAND = 3;
+    static final int ABORT_REFUSED = 4;
+    static final int ABORT_UNABLE_TO_DETERMINE = 5;
+
+    // [Audio Status]
+    static final int SYSTEM_AUDIO_STATUS_OFF = 0;
+    static final int SYSTEM_AUDIO_STATUS_ON = 1;
+
+    // Constants related to UI Command Codes.
+    // Refer to CEC Table 30 in HDMI Spec v1.4b.
+    static final int UI_COMMAND_MUTE = 0x43;
+    static final int UI_COMMAND_MUTE_FUNCTION = 0x65;
+    static final int UI_COMMAND_RESTORE_VOLUME_FUNCTION = 0x66;
+
+    // Flags used for setOption to CEC HAL.
+    /**
+     * When set to false, HAL does not wake up the system upon receiving
+     * <Image View On> or <Text View On>. Used when user changes the TV
+     * settings to disable the auto TV on functionality.
+     * True by default.
+     */
+    static final int FLAG_HDMI_OPTION_WAKEUP = 1;
+    /**
+     * When set to false, all the CEC commands are discarded. Used when
+     * user changes the TV settings to disable CEC functionality.
+     * True by default.
+     */
+    static final int FLAG_HDMI_OPTION_ENABLE_CEC = 2;
+    /**
+     * Setting this flag to false means Android system will stop handling
+     * CEC service and yield the control over to the microprocessor that is
+     * powered on through the standby mode. When set to true, the system
+     * will gain the control over, hence telling the microprocessor to stop
+     * handling the cec commands. This is called when system goes
+     * in and out of standby mode to notify the microprocessor that it should
+     * start/stop handling CEC commands on behalf of the system.
+     * False by default.
+     */
+    static final int FLAG_HDMI_OPTION_SYSTEM_CEC_CONTROL = 3;
+
+    private HdmiConstants() { /* cannot be instantiated */ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index d775733..d41da30 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -24,24 +24,25 @@
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.hdmi.IHdmiControlService;
 import android.hardware.hdmi.IHdmiHotplugEventListener;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.SystemService;
 import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
-import com.android.server.hdmi.HdmiCecLocalDevice.AddressAllocationCallback;
+import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
 
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Locale;
 
 /**
  * Provides a service for sending and processing HDMI control messages,
@@ -57,6 +58,14 @@
     static final int SEND_RESULT_NAK = -1;
     static final int SEND_RESULT_FAILURE = -2;
 
+    static final int POLL_STRATEGY_MASK = 0x3;  // first and second bit.
+    static final int POLL_STRATEGY_REMOTES_DEVICES = 0x1;
+    static final int POLL_STRATEGY_SYSTEM_AUDIO = 0x2;
+
+    static final int POLL_ITERATION_STRATEGY_MASK = 0x30000;  // first and second bit.
+    static final int POLL_ITERATION_IN_ORDER = 0x10000;
+    static final int POLL_ITERATION_REVERSE_ORDER = 0x20000;
+
     /**
      * Interface to report send result.
      */
@@ -108,18 +117,25 @@
     private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
             new ArrayList<>();
 
+    private final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache();
+
     @Nullable
     private HdmiCecController mCecController;
 
     @Nullable
     private HdmiMhlController mMhlController;
 
+    @GuardedBy("mLock")
     // Whether ARC is "enabled" or not.
     // TODO: it may need to hold lock if it's accessed from others.
     private boolean mArcStatusEnabled = false;
 
+    @GuardedBy("mLock")
+    // Whether SystemAudioMode is "On" or not.
+    private boolean mSystemAudioMode;
+
     // Handler running on service thread. It's used to run a task in service thread.
-    private Handler mHandler = new Handler();
+    private final Handler mHandler = new Handler();
 
     public HdmiControlService(Context context) {
         super(context);
@@ -131,23 +147,9 @@
     public void onStart() {
         mIoThread.start();
         mCecController = HdmiCecController.create(this);
+
         if (mCecController != null) {
-            mCecController.initializeLocalDevices(mLocalDevices, new AddressAllocationCallback() {
-                private final SparseIntArray mAllocated = new SparseIntArray();
-
-                @Override
-                public void onAddressAllocated(int deviceType, int logicalAddress) {
-                    mAllocated.append(deviceType, logicalAddress);
-                    // TODO: get HdmiLCecLocalDevice and call onAddressAllocated here.
-
-                    // Once all logical allocation is done, launch device discovery
-                    // action if one of local device is TV.
-                    int tvAddress = mAllocated.get(HdmiCec.DEVICE_TV, -1);
-                    if (mLocalDevices.length == mAllocated.size() && tvAddress != -1) {
-                        launchDeviceDiscovery(tvAddress);
-                    }
-                }
-            });
+            initializeLocalDevices(mLocalDevices);
         } else {
             Slog.i(TAG, "Device does not support HDMI-CEC.");
         }
@@ -158,6 +160,49 @@
         }
 
         publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
+
+        // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and
+        // start to monitor the preference value and invoke SystemAudioActionFromTv if needed.
+    }
+
+    private void initializeLocalDevices(final int[] deviceTypes) {
+        // A container for [Logical Address, Local device info].
+        final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>();
+        final SparseIntArray finished = new SparseIntArray();
+        for (int type : deviceTypes) {
+            final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type);
+            localDevice.init();
+            mCecController.allocateLogicalAddress(type,
+                    localDevice.getPreferredAddress(), new AllocateAddressCallback() {
+                @Override
+                public void onAllocated(int deviceType, int logicalAddress) {
+                    if (logicalAddress == HdmiCec.ADDR_UNREGISTERED) {
+                        Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
+                    } else {
+                        HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType);
+                        localDevice.setDeviceInfo(deviceInfo);
+                        mCecController.addLocalDevice(deviceType, localDevice);
+                        mCecController.addLogicalAddress(logicalAddress);
+                        devices.append(logicalAddress, localDevice);
+                    }
+                    finished.append(deviceType, logicalAddress);
+
+                    // Once finish address allocation for all devices, notify
+                    // it to each device.
+                    if (deviceTypes.length == finished.size()) {
+                        notifyAddressAllocated(devices);
+                    }
+                }
+            });
+        }
+    }
+
+    private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) {
+        for (int i = 0; i < devices.size(); ++i) {
+            int address = devices.keyAt(i);
+            HdmiCecLocalDevice device = devices.valueAt(i);
+            device.onAddressAllocated(address);
+        }
     }
 
     /**
@@ -180,6 +225,37 @@
     }
 
     /**
+     * Returns physical address of the device.
+     */
+    int getPhysicalAddress() {
+        return mCecController.getPhysicalAddress();
+    }
+
+    /**
+     * Returns vendor id of CEC service.
+     */
+    int getVendorId() {
+        return mCecController.getVendorId();
+    }
+
+    /**
+     * Returns version of CEC.
+     */
+    int getCecVersion() {
+        return mCecController.getVersion();
+    }
+
+    /**
+     * Returns a list of {@link HdmiCecDeviceInfo}.
+     *
+     * @param includeLocalDevice whether to include local devices
+     */
+    List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
+        assertRunOnServiceThread();
+        return mCecController.getDeviceInfoList(includeLocalDevice);
+    }
+
+    /**
      * Add and start a new {@link FeatureAction} to the action queue.
      *
      * @param action {@link FeatureAction} to add and start
@@ -195,8 +271,20 @@
         });
     }
 
+    void setSystemAudioMode(boolean on) {
+        synchronized (mLock) {
+            mSystemAudioMode = on;
+        }
+    }
+
+    boolean getSystemAudioMode() {
+        synchronized (mLock) {
+            return mSystemAudioMode;
+        }
+    }
+
     // See if we have an action of a given type in progress.
-    private <T extends FeatureAction> boolean hasAction(final Class<T> clazz) {
+    <T extends FeatureAction> boolean hasAction(final Class<T> clazz) {
         for (FeatureAction action : mActions) {
             if (action.getClass().equals(clazz)) {
                 return true;
@@ -211,48 +299,61 @@
      * @param action {@link FeatureAction} to remove
      */
     void removeAction(final FeatureAction action) {
-        runOnServiceThread(new Runnable() {
-            @Override
-            public void run() {
-                mActions.remove(action);
-            }
-        });
+        assertRunOnServiceThread();
+        mActions.remove(action);
     }
 
     // Remove all actions matched with the given Class type.
     private <T extends FeatureAction> void removeAction(final Class<T> clazz) {
-        runOnServiceThread(new Runnable() {
-            @Override
-            public void run() {
-                Iterator<FeatureAction> iter = mActions.iterator();
-                while (iter.hasNext()) {
-                    FeatureAction action = iter.next();
-                    if (action.getClass().equals(clazz)) {
-                        action.clear();
-                        mActions.remove(action);
-                    }
-                }
+        removeActionExcept(clazz, null);
+    }
+
+    // Remove all actions matched with the given Class type besides |exception|.
+    <T extends FeatureAction> void removeActionExcept(final Class<T> clazz,
+            final FeatureAction exception) {
+        assertRunOnServiceThread();
+        Iterator<FeatureAction> iter = mActions.iterator();
+        while (iter.hasNext()) {
+            FeatureAction action = iter.next();
+            if (action != exception && action.getClass().equals(clazz)) {
+                action.clear();
+                mActions.remove(action);
             }
-        });
+        }
     }
 
     private void runOnServiceThread(Runnable runnable) {
         mHandler.post(runnable);
     }
 
+    void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
+        mHandler.postAtFrontOfQueue(runnable);
+    }
+
+    private void assertRunOnServiceThread() {
+        if (Looper.myLooper() != mHandler.getLooper()) {
+            throw new IllegalStateException("Should run on service thread.");
+        }
+    }
+
     /**
      * Change ARC status into the given {@code enabled} status.
      *
      * @return {@code true} if ARC was in "Enabled" status
      */
     boolean setArcStatus(boolean enabled) {
-        boolean oldStatus = mArcStatusEnabled;
-        // 1. Enable/disable ARC circuit.
-        // TODO: call set_audio_return_channel of hal interface.
+        assertRunOnServiceThread();
+        synchronized (mLock) {
+            boolean oldStatus = mArcStatusEnabled;
+            // 1. Enable/disable ARC circuit.
+            mCecController.setAudioReturnChannel(enabled);
 
-        // 2. Update arc status;
-        mArcStatusEnabled = enabled;
-        return oldStatus;
+            // TODO: notify arc mode change to AudioManager.
+
+            // 2. Update arc status;
+            mArcStatusEnabled = enabled;
+            return oldStatus;
+        }
     }
 
     /**
@@ -269,48 +370,43 @@
         mCecController.sendCommand(command, null);
     }
 
-    /**
-     * Add a new {@link HdmiCecDeviceInfo} to controller.
-     *
-     * @param deviceInfo new device information object to add
-     */
-    void addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
-        // TODO: Implement this.
-    }
-
     boolean handleCecCommand(HdmiCecMessage message) {
+        // Cache incoming message. Note that it caches only white-listed one.
+        mCecMessageCache.cacheMessage(message);
+
         // Commands that queries system information replies directly instead
         // of creating FeatureAction because they are state-less.
+        // TODO: move the leftover message to local device.
         switch (message.getOpcode()) {
-            case HdmiCec.MESSAGE_GET_MENU_LANGUAGE:
-                handleGetMenuLanguage(message);
-                return true;
-            case HdmiCec.MESSAGE_GIVE_OSD_NAME:
-                handleGiveOsdName(message);
-                return true;
-            case HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS:
-                handleGivePhysicalAddress(message);
-                return true;
-            case HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID:
-                handleGiveDeviceVendorId(message);
-                return true;
-            case HdmiCec.MESSAGE_GET_CEC_VERSION:
-                handleGetCecVersion(message);
-                return true;
             case HdmiCec.MESSAGE_INITIATE_ARC:
                 handleInitiateArc(message);
                 return true;
             case HdmiCec.MESSAGE_TERMINATE_ARC:
                 handleTerminateArc(message);
                 return true;
-            case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS:
-                handleReportPhysicalAddress(message);
+            case HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE:
+                handleSetSystemAudioMode(message);
                 return true;
-            // TODO: Add remaining system information query such as
-            // <Give Device Power Status> and <Request Active Source> handler.
+            case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
+                handleSystemAudioModeStatus(message);
+                return true;
             default:
-                return dispatchMessageToAction(message);
+                if (dispatchMessageToAction(message)) {
+                    return true;
+                }
+                break;
         }
+
+        return dispatchMessageToLocalDevice(message);
+    }
+
+    private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
+        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+            if (device.dispatchMessage(message)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
@@ -328,26 +424,62 @@
      * devices.
      *
      * @param callback an interface used to get a list of all remote devices' address
+     * @param pickStrategy strategy how to pick polling candidates
      * @param retryCount the number of retry used to send polling message to remote devices
+     * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
      */
-    void pollDevices(DevicePollingCallback callback, int retryCount) {
-        mCecController.pollDevices(callback, retryCount);
+    void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) {
+        mCecController.pollDevices(callback, checkPollStrategy(pickStrategy), retryCount);
     }
 
-    private void handleReportPhysicalAddress(HdmiCecMessage message) {
-        // At first, try to consume it.
-        if (dispatchMessageToAction(message)) {
-            return;
+    private int checkPollStrategy(int pickStrategy) {
+        int strategy = pickStrategy & POLL_STRATEGY_MASK;
+        if (strategy == 0) {
+            throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
         }
-
-        // Ignore if [Device Discovery Action] is on going ignore message.
-        if (hasAction(DeviceDiscoveryAction.class)) {
-            Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> "
-                    + "because Device Discovery Action is on-going:" + message);
-            return;
+        int iterationStrategy = pickStrategy & POLL_ITERATION_STRATEGY_MASK;
+        if (iterationStrategy == 0) {
+            throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
         }
+        return strategy | iterationStrategy;
+    }
 
-        // TODO: start new device action.
+    /**
+     * Launch device discovery sequence. It starts with clearing the existing device info list.
+     * Note that it assumes that logical address of all local devices is already allocated.
+     *
+     * @param sourceAddress a logical address of tv
+     */
+    void launchDeviceDiscovery(final int sourceAddress) {
+        // At first, clear all existing device infos.
+        mCecController.clearDeviceInfoList();
+        // TODO: flush cec message cache when CEC is turned off.
+
+        DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, sourceAddress,
+                new DeviceDiscoveryCallback() {
+                    @Override
+                    public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) {
+                        for (HdmiCecDeviceInfo info : deviceInfos) {
+                            addCecDevice(info);
+                        }
+
+                        // Add device info of all local devices.
+                        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+                            addCecDevice(device.getDeviceInfo());
+                        }
+
+                        addAndStartAction(new HotplugDetectionAction(HdmiControlService.this,
+                                sourceAddress));
+                    }
+                });
+        addAndStartAction(action);
+    }
+
+    private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) {
+        // TODO: find better name instead of model name.
+        String displayName = Build.MODEL;
+        return new HdmiCecDeviceInfo(logicalAddress,
+                getPhysicalAddress(), deviceType, getVendorId(), displayName);
     }
 
     private void handleInitiateArc(HdmiCecMessage message){
@@ -370,64 +502,6 @@
         addAndStartAction(action);
     }
 
-    private void handleGetCecVersion(HdmiCecMessage message) {
-        int version = mCecController.getVersion();
-        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
-                message.getSource(),
-                version);
-        sendCecCommand(cecMessage);
-    }
-
-    private void handleGiveDeviceVendorId(HdmiCecMessage message) {
-        int vendorId = mCecController.getVendorId();
-        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
-                message.getDestination(), vendorId);
-        sendCecCommand(cecMessage);
-    }
-
-    private void handleGivePhysicalAddress(HdmiCecMessage message) {
-        int physicalAddress = mCecController.getPhysicalAddress();
-        int deviceType = HdmiCec.getTypeFromAddress(message.getDestination());
-        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
-                message.getDestination(), physicalAddress, deviceType);
-        sendCecCommand(cecMessage);
-    }
-
-    private void handleGiveOsdName(HdmiCecMessage message) {
-        // TODO: read device name from settings or property.
-        String name = HdmiCec.getDefaultDeviceName(message.getDestination());
-        HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand(
-                message.getDestination(), message.getSource(), name);
-        if (cecMessage != null) {
-            sendCecCommand(cecMessage);
-        } else {
-            Slog.w(TAG, "Failed to build <Get Osd Name>:" + name);
-        }
-    }
-
-    private void handleGetMenuLanguage(HdmiCecMessage message) {
-        // Only 0 (TV), 14 (specific use) can answer.
-        if (message.getDestination() != HdmiCec.ADDR_TV
-                && message.getDestination() != HdmiCec.ADDR_SPECIFIC_USE) {
-            Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
-            sendCecCommand(
-                    HdmiCecMessageBuilder.buildFeatureAbortCommand(message.getDestination(),
-                            message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE,
-                            HdmiCecMessageBuilder.ABORT_UNRECOGNIZED_MODE));
-            return;
-        }
-
-        HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
-                message.getDestination(),
-                Locale.getDefault().getISO3Language());
-        // TODO: figure out how to handle failed to get language code.
-        if (command != null) {
-            sendCecCommand(command);
-        } else {
-            Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
-        }
-    }
-
     private boolean dispatchMessageToAction(HdmiCecMessage message) {
         for (FeatureAction action : mActions) {
             if (action.processCommand(message)) {
@@ -438,6 +512,33 @@
         return false;
     }
 
+    private void handleSetSystemAudioMode(HdmiCecMessage message) {
+        if (dispatchMessageToAction(message) || !isMessageForSystemAudio(message)) {
+            return;
+        }
+        SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
+                message.getDestination(), message.getSource(),
+                HdmiUtils.parseCommandParamSystemAudioStatus(message));
+        addAndStartAction(action);
+    }
+
+    private void handleSystemAudioModeStatus(HdmiCecMessage message) {
+        if (!isMessageForSystemAudio(message)) {
+            return;
+        }
+        setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message));
+    }
+
+    private boolean isMessageForSystemAudio(HdmiCecMessage message) {
+        if (message.getSource() != HdmiCec.ADDR_AUDIO_SYSTEM
+                || message.getDestination() != HdmiCec.ADDR_TV
+                || getAvrDeviceInfo() == null) {
+            Slog.w(TAG, "Skip abnormal CecMessage: " + message);
+            return false;
+        }
+        return true;
+    }
+
     // Record class that monitors the event of the caller of being killed. Used to clean up
     // the listener list and record list accordingly.
     private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
@@ -460,34 +561,6 @@
         mCecController.addDeviceInfo(info);
     }
 
-    // Launch device discovery sequence.
-    // It starts with clearing the existing device info list.
-    // Note that it assumes that logical address of all local devices is already allocated.
-    private void launchDeviceDiscovery(int sourceAddress) {
-        // At first, clear all existing device infos.
-        mCecController.clearDeviceInfoList();
-
-        // TODO: check whether TV is one of local devices.
-        DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, sourceAddress,
-                new DeviceDiscoveryCallback() {
-                    @Override
-                    public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) {
-                        for (HdmiCecDeviceInfo info : deviceInfos) {
-                            mCecController.addDeviceInfo(info);
-                        }
-
-                        // Add device info of all local devices.
-                        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
-                            mCecController.addDeviceInfo(device.getDeviceInfo());
-                        }
-
-                        // TODO: start hot-plug detection sequence here.
-                        // addAndStartAction(new HotplugDetectionAction());
-                    }
-                });
-        addAndStartAction(action);
-    }
-
     private void enforceAccessPermission() {
         getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
     }
@@ -627,4 +700,31 @@
             Slog.e(TAG, "Invoking callback failed:" + e);
         }
     }
+
+    HdmiCecDeviceInfo getAvrDeviceInfo() {
+        return mCecController.getDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM);
+    }
+
+    void setAudioStatus(boolean mute, int volume) {
+        // TODO: Hook up with AudioManager.
+    }
+
+    boolean isInPresetInstallationMode() {
+        // TODO: Implement this.
+        return false;
+    }
+
+    /**
+     * Called when a device is removed or removal of device is detected.
+     *
+     * @param address a logical address of a device to be removed
+     */
+    void removeCecDevice(int address) {
+        mCecController.removeDeviceInfo(address);
+        mCecMessageCache.flushMessagesFrom(address);
+    }
+
+    HdmiCecMessageCache getCecMessageCache() {
+        return mCecMessageCache;
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java
new file mode 100644
index 0000000..ca09fe6
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.util.Slog;
+
+/**
+ * Various utilities to handle HDMI CEC messages.
+ */
+final class HdmiUtils {
+
+    private HdmiUtils() { /* cannot be instantiated */ }
+
+    /**
+     * Verify if the given address is for the given device type.  If not it will throw
+     * {@link IllegalArgumentException}.
+     *
+     * @param logicalAddress the logical address to verify
+     * @param deviceType the device type to check
+     * @throw IllegalArgumentException
+     */
+    static void verifyAddressType(int logicalAddress, int deviceType) {
+        int actualDeviceType = HdmiCec.getTypeFromAddress(logicalAddress);
+        if (actualDeviceType != deviceType) {
+            throw new IllegalArgumentException("Device type missmatch:[Expected:" + deviceType
+                    + ", Actual:" + actualDeviceType);
+        }
+    }
+
+    /**
+     * Check if the given CEC message come from the given address.
+     *
+     * @param cmd the CEC message to check
+     * @param expectedAddress the expected source address of the given message
+     * @param tag the tag of caller module (for log message)
+     * @return true if the CEC message comes from the given address
+     */
+    static boolean checkCommandSource(HdmiCecMessage cmd, int expectedAddress, String tag) {
+        int src = cmd.getSource();
+        if (src != expectedAddress) {
+            Slog.w(tag, "Invalid source [Expected:" + expectedAddress + ", Actual:" + src + "]");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Parse the parameter block of CEC message as [System Audio Status].
+     *
+     * @param cmd the CEC message to parse
+     * @return true if the given parameter has [ON] value
+     */
+    static boolean parseCommandParamSystemAudioStatus(HdmiCecMessage cmd) {
+        // TODO: Handle the exception when the length is wrong.
+        return cmd.getParams().length > 0
+                && cmd.getParams()[0] == HdmiConstants.SYSTEM_AUDIO_STATUS_ON;
+    }
+
+    /**
+     * Assemble two bytes into single integer value.
+     *
+     * @param data to be assembled
+     * @return assembled value
+     */
+    static int twoBytesToInt(byte[] data) {
+        return ((data[0] & 0xFF) << 8) | (data[1] & 0xFF);
+    }
+
+    /**
+     * Assemble three bytes into single integer value.
+     *
+     * @param data to be assembled
+     * @return assembled value
+     */
+    static int threeBytesToInt(byte[] data) {
+        return ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
new file mode 100644
index 0000000..c7a813d
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.util.Slog;
+
+import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
+
+import java.util.BitSet;
+import java.util.List;
+
+/**
+ * Feature action that handles hot-plug detection mechanism.
+ * Hot-plug event is initiated by timer after device discovery action.
+ *
+ * <p>Check all devices every 15 secs except for system audio.
+ * If system audio is on, check hot-plug for audio system every 5 secs.
+ * For other devices, keep 15 secs period.
+ */
+final class HotplugDetectionAction extends FeatureAction {
+    private static final String TAG = "HotPlugDetectionAction";
+
+    private static final int POLLING_INTERVAL_MS = 5000;
+    private static final int TIMEOUT_COUNT = 3;
+    private static final int POLL_RETRY_COUNT = 2;
+
+    // State in which waits for next polling
+    private static final int STATE_WAIT_FOR_NEXT_POLLING = 1;
+
+    // All addresses except for broadcast (unregistered address).
+    private static final int NUM_OF_ADDRESS = HdmiCec.ADDR_SPECIFIC_USE - HdmiCec.ADDR_TV + 1;
+
+    private int mTimeoutCount = 0;
+
+    /**
+     * Constructor
+     *
+     * @param service instance of {@link HdmiControlService}
+     * @param sourceAddress logical address of a device that initiate this action
+     */
+    HotplugDetectionAction(HdmiControlService service, int sourceAddress) {
+        super(service, sourceAddress);
+    }
+
+    @Override
+    boolean start() {
+        Slog.v(TAG, "Hot-plug dection started.");
+
+        mState = STATE_WAIT_FOR_NEXT_POLLING;
+        mTimeoutCount = 0;
+
+        // Start timer without polling.
+        // The first check for all devices will be initiated 15 seconds later.
+        addTimer(mState, POLLING_INTERVAL_MS);
+        return true;
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        // No-op
+        return false;
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        if (mState != state) {
+            return;
+        }
+
+        if (mState == STATE_WAIT_FOR_NEXT_POLLING) {
+            mTimeoutCount = (mTimeoutCount + 1) % TIMEOUT_COUNT;
+            pollDevices();
+        }
+    }
+
+    // This method is called every 5 seconds.
+    private void pollDevices() {
+        // All device check called every 15 seconds.
+        if (mTimeoutCount == 0) {
+            pollAllDevices();
+        } else {
+            if (mService.getSystemAudioMode()) {
+                pollAudioSystem();
+            }
+        }
+
+        addTimer(mState, POLLING_INTERVAL_MS);
+    }
+
+    private void pollAllDevices() {
+        Slog.v(TAG, "Poll all devices.");
+
+        mService.pollDevices(new DevicePollingCallback() {
+            @Override
+            public void onPollingFinished(List<Integer> ackedAddress) {
+                checkHotplug(ackedAddress, false);
+            }
+        }, HdmiControlService.POLL_ITERATION_IN_ORDER
+                | HdmiControlService.POLL_STRATEGY_REMOTES_DEVICES, POLL_RETRY_COUNT);
+    }
+
+    private void pollAudioSystem() {
+        Slog.v(TAG, "Poll audio system.");
+
+        mService.pollDevices(new DevicePollingCallback() {
+            @Override
+            public void onPollingFinished(List<Integer> ackedAddress) {
+                checkHotplug(ackedAddress, true);
+            }
+        }, HdmiControlService.POLL_ITERATION_IN_ORDER
+                | HdmiControlService.POLL_STRATEGY_SYSTEM_AUDIO, POLL_RETRY_COUNT);
+    }
+
+    private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) {
+        BitSet currentInfos = infoListToBitSet(mService.getDeviceInfoList(false), audioOnly);
+        BitSet polledResult = addressListToBitSet(ackedAddress);
+
+        // At first, check removed devices.
+        BitSet removed = complement(currentInfos, polledResult);
+        int index = -1;
+        while ((index = removed.nextSetBit(index + 1)) != -1) {
+            Slog.v(TAG, "Remove device by hot-plug detection:" + index);
+            removeDevice(index);
+        }
+
+        // Next, check added devices.
+        BitSet added = complement(polledResult, currentInfos);
+        index = -1;
+        while ((index = added.nextSetBit(index + 1)) != -1) {
+            Slog.v(TAG, "Add device by hot-plug detection:" + index);
+            addDevice(index);
+        }
+    }
+
+    private static BitSet infoListToBitSet(List<HdmiCecDeviceInfo> infoList, boolean audioOnly) {
+        BitSet set = new BitSet(NUM_OF_ADDRESS);
+        for (HdmiCecDeviceInfo info : infoList) {
+            if (audioOnly) {
+                if (info.getDeviceType() == HdmiCec.DEVICE_AUDIO_SYSTEM) {
+                    set.set(info.getLogicalAddress());
+                }
+            } else {
+                set.set(info.getLogicalAddress());
+            }
+        }
+        return set;
+    }
+
+    private static BitSet addressListToBitSet(List<Integer> list) {
+        BitSet set = new BitSet(NUM_OF_ADDRESS);
+        for (Integer value : list) {
+            set.set(value);
+        }
+        return set;
+    }
+
+    // A - B = A & ~B
+    private static BitSet complement(BitSet first, BitSet second) {
+        // Need to clone it so that it doesn't touch original set.
+        BitSet clone = (BitSet) first.clone();
+        clone.andNot(second);
+        return clone;
+    }
+
+    private void addDevice(int addedAddress) {
+        // TODO: implement this.
+    }
+
+    private void removeDevice(int removedAddress) {
+        mService.removeCecDevice(removedAddress);
+        // TODO: implements following steps.
+        // 1. Launch routing control sequence
+        // 2. Stop one touch play sequence if removed device is the device to be selected.
+        // 3. If audio system, start system audio off and arc off
+        // 4. Inform device remove to others
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index 156bbbe..c284d10 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -70,10 +70,13 @@
 
     @Override
     public boolean start() {
-        sendCommand(
-                HdmiCecMessageBuilder.buildGiveOsdNameCommand(mSourceAddress,
-                        mDeviceLogicalAddress));
         mState = STATE_WAITING_FOR_SET_OSD_NAME;
+        if (mayProcessCommandIfCached(mDeviceLogicalAddress, HdmiCec.MESSAGE_SET_OSD_NAME)) {
+            return true;
+        }
+
+        sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(mSourceAddress,
+                mDeviceLogicalAddress));
         addTimer(mState, TIMEOUT_MS);
         return true;
     }
@@ -99,13 +102,11 @@
                 } catch (UnsupportedEncodingException e) {
                     Slog.e(TAG, "Failed to get OSD name: " + e.getMessage());
                 }
-                mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID;
                 requestVendorId();
                 return true;
             } else if (opcode == HdmiCec.MESSAGE_FEATURE_ABORT) {
-                int requestOpcode = params[1] & 0xff;
+                int requestOpcode = params[1] & 0xFF;
                 if (requestOpcode == HdmiCec.MESSAGE_SET_OSD_NAME) {
-                    mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID;
                     requestVendorId();
                     return true;
                 }
@@ -113,8 +114,7 @@
         } else if (mState == STATE_WAITING_FOR_DEVICE_VENDOR_ID) {
             if (opcode == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) {
                 if (params.length == 3) {
-                    mVendorId = ((params[0] & 0xff) << 16) + ((params[1] & 0xff) << 8)
-                        + (params[2] & 0xff);
+                    mVendorId = HdmiUtils.threeBytesToInt(params);
                 } else {
                     Slog.e(TAG, "Failed to get device vendor ID: ");
                 }
@@ -122,7 +122,7 @@
                 finish();
                 return true;
             } else if (opcode == HdmiCec.MESSAGE_FEATURE_ABORT) {
-                int requestOpcode = params[1] & 0xff;
+                int requestOpcode = params[1] & 0xFF;
                 if (requestOpcode == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) {
                     addDeviceInfo();
                     finish();
@@ -133,7 +133,21 @@
         return false;
     }
 
+    private boolean mayProcessCommandIfCached(int destAddress, int opcode) {
+        HdmiCecMessage message = mService.getCecMessageCache().getMessage(destAddress, opcode);
+        if (message != null) {
+            return processCommand(message);
+        }
+        return false;
+    }
+
     private void requestVendorId() {
+        // At first, transit to waiting status for <Device Vendor Id>.
+        mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID;
+        // If the message is already in cache, process it.
+        if (mayProcessCommandIfCached(mDeviceLogicalAddress, HdmiCec.MESSAGE_DEVICE_VENDOR_ID)) {
+            return;
+        }
         sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(mSourceAddress,
                 mDeviceLogicalAddress));
         addTimer(mState, TIMEOUT_MS);
@@ -143,7 +157,7 @@
         if (mDisplayName == null) {
             mDisplayName = HdmiCec.getDefaultDeviceName(mDeviceLogicalAddress);
         }
-        mService.addDeviceInfo(new HdmiCecDeviceInfo(
+        mService.addCecDevice(new HdmiCecDeviceInfo(
                 mDeviceLogicalAddress, mDevicePhysicalAddress,
                 HdmiCec.getTypeFromAddress(mDeviceLogicalAddress),
                 mVendorId, mDisplayName));
@@ -156,7 +170,6 @@
         }
         if (state == STATE_WAITING_FOR_SET_OSD_NAME) {
             // Osd name request timed out. Try vendor id
-            mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID;
             requestVendorId();
         } else if (state == STATE_WAITING_FOR_DEVICE_VENDOR_ID) {
             // vendor id timed out. Go ahead creating the device info what we've got so far.
diff --git a/services/core/java/com/android/server/hdmi/RequestArcAction.java b/services/core/java/com/android/server/hdmi/RequestArcAction.java
index 05614a4..08ca306 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcAction.java
@@ -44,28 +44,15 @@
      */
     RequestArcAction(HdmiControlService service, int sourceAddress, int avrAddress) {
         super(service, sourceAddress);
-        verifyAddressType(sourceAddress, HdmiCec.DEVICE_TV);
-        verifyAddressType(avrAddress, HdmiCec.DEVICE_AUDIO_SYSTEM);
+        HdmiUtils.verifyAddressType(sourceAddress, HdmiCec.DEVICE_TV);
+        HdmiUtils.verifyAddressType(avrAddress, HdmiCec.DEVICE_AUDIO_SYSTEM);
         mAvrAddress = avrAddress;
     }
 
-    private static void verifyAddressType(int logicalAddress, int deviceType) {
-        int actualDeviceType = HdmiCec.getTypeFromAddress(logicalAddress);
-        if (actualDeviceType != deviceType) {
-            throw new IllegalArgumentException("Device type missmatch:[Expected:" + deviceType
-                    + ", Actual:" + actualDeviceType);
-        }
-    }
-
     @Override
     boolean processCommand(HdmiCecMessage cmd) {
-        if (mState != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE) {
-            return false;
-        }
-
-        int src = cmd.getSource();
-        if (src != mAvrAddress) {
-            Slog.w(TAG, "Invalid source [Expected:" + mAvrAddress + ", Actual:" + src + "]");
+        if (mState != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE
+                || !HdmiUtils.checkCommandSource(cmd, mAvrAddress, TAG)) {
             return false;
         }
         int opcode = cmd.getOpcode();
diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java
new file mode 100644
index 0000000..5eb9315
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCecMessage;
+import android.view.KeyEvent;
+import android.util.Slog;
+
+import libcore.util.EmptyArray;
+
+/**
+ * Feature action that transmits remote control key command (User Control Press/
+ * User Control Release) to CEC bus.
+ *
+ * <p>This action is created when a new key event is passed to CEC service. It optionally
+ * does key repeat (a.k.a. press-and-hold) operation until it receives a key release event.
+ * If another key press event is received before the key in use is released, CEC service
+ * does not create a new action but recycles the current one by updating the key used
+ * for press-and-hold operation.
+ *
+ * <p>Package-private, accessed by {@link HdmiControlService} only.
+ */
+final class SendKeyAction extends FeatureAction {
+    private static final String TAG = "SendKeyAction";
+
+    // State in which the action is at work. The state is set in {@link #start()} and
+    // persists throughout the process till it is set back to {@code STATE_NONE} at the end.
+    private static final int STATE_PROCESSING_KEYCODE = 1;
+
+    // IRT(Initiator Repetition Time) in millisecond as recommended in the standard.
+    // Outgoing UCP commands, when in 'Press and Hold' mode, should be this much apart
+    // from the adjacent one so as not to place unnecessarily heavy load on the CEC line.
+    // TODO: This value might need tweaking per product basis. Consider putting it
+    //       in config.xml to allow customization.
+    private static final int IRT_MS = 450;
+
+    // Logical address of the device to which the UCP/UCP commands are sent.
+    private final int mTargetAddress;
+
+    // The key code of the last key press event the action is passed via processKeyEvent.
+    private int mLastKeyCode;
+
+    /**
+     * Constructor.
+     *
+     * @param service {@link HdmiControlService} instance
+     * @param sourceAddress logical address to be used as source address
+     * @param targetAddress logical address of the device to send the keys to
+     * @param keyCode remote control key code as defined in {@link KeyEvent}
+     */
+    SendKeyAction(HdmiControlService service, int sourceAddress, int targetAddress, int keyCode) {
+        super(service, sourceAddress);
+        mTargetAddress = targetAddress;
+        mLastKeyCode = keyCode;
+    }
+
+    @Override
+    public boolean start() {
+        sendKeyDown(mLastKeyCode);
+        mState = STATE_PROCESSING_KEYCODE;
+        addTimer(mState, IRT_MS);
+        return true;
+    }
+
+    /**
+     * Called when a key event should be handled for the action.
+     *
+     * @param keyCode key code of {@link KeyEvent} object
+     * @param isPressed true if the key event is of {@link KeyEvent#ACTION_DOWN}
+     * @param param additional parameter that comes with the key event
+     */
+    void processKeyEvent(int keyCode, boolean isPressed) {
+        if (mState != STATE_PROCESSING_KEYCODE) {
+            Slog.w(TAG, "Not in a valid state");
+            return;
+        }
+        // A new key press event that comes in with a key code different from the last
+        // one sets becomes a new key code to be used for press-and-hold operation.
+        // Removes any pending timer and starts a new timer for itself.
+        // Key release event indicates that the action shall be finished. Send UCR
+        // command and terminate the action. Other release events are ignored.
+        if (isPressed) {
+            if (keyCode != mLastKeyCode) {
+                mActionTimer.clearTimerMessage();
+                sendKeyDown(keyCode);
+                addTimer(mState, IRT_MS);
+                mLastKeyCode = keyCode;
+            }
+        } else {
+            if (keyCode == mLastKeyCode) {
+                sendKeyUp();
+                finish();
+            }
+        }
+    }
+
+    private void sendKeyDown(int keyCode) {
+        byte[] keyCodeAndParam = getCecKeyCodeAndParam(keyCode);
+        if (keyCodeAndParam == null) {
+            return;
+        }
+        sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(mSourceAddress, mTargetAddress,
+                keyCodeAndParam));
+    }
+
+    private void sendKeyUp() {
+        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(mSourceAddress, mTargetAddress));
+    }
+
+    @Override
+    public boolean processCommand(HdmiCecMessage cmd) {
+        // Send key action doesn't need any incoming CEC command, hence does not consume it.
+        return false;
+    }
+
+    @Override
+    public void handleTimerEvent(int state) {
+        // Timer event occurs every IRT_MS milliseconds to perform key-repeat (or press-and-hold)
+        // operation. If the last received key code is as same as the one with which the action
+        // is started, plus there was no key release event in last IRT_MS timeframe, send a UCP
+        // command and start another timer to schedule the next press-and-hold command.
+        if (mState != STATE_PROCESSING_KEYCODE) {
+            Slog.w(TAG, "Not in a valid state");
+            return;
+        }
+        sendKeyDown(mLastKeyCode);
+        addTimer(mState, IRT_MS);
+    }
+
+    // Converts the Android key code to corresponding CEC key code definition. Those CEC keys
+    // with additional parameters should be mapped from individual Android key code. 'Select
+    // Broadcast' with the parameter 'cable', for instance, shall have its counterpart such as
+    // KeyEvent.KEYCODE_TV_BROADCAST_CABLE.
+    // The return byte array contains both UI command (keycode) and optional parameter.
+    private byte[] getCecKeyCodeAndParam(int keyCode) {
+        // TODO: Convert to CEC keycode and (optionally) parameter.
+        //       return androidKeyToCecKey(keyCode);
+        return EmptyArray.BYTE;
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
index e3525d8..d53d88d 100644
--- a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
+++ b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
@@ -46,21 +46,12 @@
     SetArcTransmissionStateAction(HdmiControlService service, int sourceAddress, int avrAddress,
             boolean enabled) {
         super(service, sourceAddress);
-        verifyAddressType(sourceAddress, HdmiCec.DEVICE_TV);
-        verifyAddressType(avrAddress, HdmiCec.DEVICE_AUDIO_SYSTEM);
+        HdmiUtils.verifyAddressType(sourceAddress, HdmiCec.DEVICE_TV);
+        HdmiUtils.verifyAddressType(avrAddress, HdmiCec.DEVICE_AUDIO_SYSTEM);
         mAvrAddress = avrAddress;
         mEnabled = enabled;
     }
 
-    // TODO: extract it as separate utility class.
-    private static void verifyAddressType(int logicalAddress, int deviceType) {
-        int actualDeviceType = HdmiCec.getTypeFromAddress(logicalAddress);
-        if (actualDeviceType != deviceType) {
-            throw new IllegalArgumentException("Device type missmatch:[Expected:" + deviceType
-                    + ", Actual:" + actualDeviceType);
-        }
-    }
-
     @Override
     boolean start() {
         if (mEnabled) {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
new file mode 100644
index 0000000..dde3342
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+
+/**
+ * Base feature action class for SystemAudioActionFromTv and SystemAudioActionFromAvr.
+ */
+abstract class SystemAudioAction extends FeatureAction {
+    private static final String TAG = "SystemAudioAction";
+
+    // State in which waits for <SetSystemAudioMode>.
+    private static final int STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE = 1;
+
+    // State in which waits for <ReportAudioStatus>.
+    private static final int STATE_WAIT_FOR_REPORT_AUDIO_STATUS = 2;
+
+    private static final int MAX_SEND_RETRY_COUNT = 2;
+
+    private static final int ON_TIMEOUT_MS = 5000;
+    private static final int OFF_TIMEOUT_MS = TIMEOUT_MS;
+
+    // Logical address of AV Receiver.
+    protected final int mAvrLogicalAddress;
+
+    // The target audio status of the action, whether to enable the system audio mode or not.
+    protected boolean mTargetAudioStatus;
+
+    private int mSendRetryCount = 0;
+
+    /**
+     * Constructor
+     *
+     * @param service {@link HdmiControlService} instance
+     * @param sourceAddress logical address of source device (TV or STB).
+     * @param avrAddress logical address of AVR device
+     * @param targetStatus Whether to enable the system audio mode or not
+     * @throw IllegalArugmentException if device type of sourceAddress and avrAddress is invalid
+     */
+    SystemAudioAction(HdmiControlService service, int sourceAddress, int avrAddress,
+            boolean targetStatus) {
+        super(service, sourceAddress);
+        HdmiUtils.verifyAddressType(avrAddress, HdmiCec.DEVICE_AUDIO_SYSTEM);
+        mAvrLogicalAddress = avrAddress;
+        mTargetAudioStatus = targetStatus;
+    }
+
+    protected void sendSystemAudioModeRequest() {
+        int avrPhysicalAddress = mService.getAvrDeviceInfo().getPhysicalAddress();
+        HdmiCecMessage command = HdmiCecMessageBuilder.buildSystemAudioModeRequest(mSourceAddress,
+                mAvrLogicalAddress, avrPhysicalAddress, mTargetAudioStatus);
+        sendCommand(command, new HdmiControlService.SendMessageCallback() {
+            @Override
+            public void onSendCompleted(int error) {
+                if (error == HdmiControlService.SEND_RESULT_SUCCESS) {
+                    mState = STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE;
+                    addTimer(mState, mTargetAudioStatus ? ON_TIMEOUT_MS : OFF_TIMEOUT_MS);
+                } else {
+                    setSystemAudioMode(false);
+                    finish();
+                }
+            }
+        });
+    }
+
+    private void handleSendSystemAudioModeRequestTimeout() {
+        if (!mTargetAudioStatus  // Don't retry for Off case.
+                || mSendRetryCount++ >= MAX_SEND_RETRY_COUNT) {
+            setSystemAudioMode(false);
+            finish();
+            return;
+        }
+        sendSystemAudioModeRequest();
+    }
+
+    protected void setSystemAudioMode(boolean mode) {
+        mService.setSystemAudioMode(mode);
+    }
+
+    protected void sendGiveAudioStatus() {
+        HdmiCecMessage command = HdmiCecMessageBuilder.buildGiveAudioStatus(mSourceAddress,
+                mAvrLogicalAddress);
+        sendCommand(command, new HdmiControlService.SendMessageCallback() {
+            @Override
+            public void onSendCompleted(int error) {
+                if (error == HdmiControlService.SEND_RESULT_SUCCESS) {
+                    mState = STATE_WAIT_FOR_REPORT_AUDIO_STATUS;
+                    addTimer(mState, TIMEOUT_MS);
+                } else {
+                    handleSendGiveAudioStatusFailure();
+                }
+            }
+        });
+    }
+
+    private void handleSendGiveAudioStatusFailure() {
+        // TODO: Notify the failure status.
+
+        int uiCommand = mService.getSystemAudioMode()
+                ? HdmiConstants.UI_COMMAND_RESTORE_VOLUME_FUNCTION  // SystemAudioMode: ON
+                : HdmiConstants.UI_COMMAND_MUTE_FUNCTION;           // SystemAudioMode: OFF
+        sendUserControlPressedAndReleased(uiCommand);
+        finish();
+    }
+
+    private void sendUserControlPressedAndReleased(int uiCommand) {
+        sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(
+                mSourceAddress, mAvrLogicalAddress, uiCommand));
+        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
+                mSourceAddress, mAvrLogicalAddress));
+    }
+
+    @Override
+    final boolean processCommand(HdmiCecMessage cmd) {
+        switch (mState) {
+            case STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE:
+                // TODO: Handle <FeatureAbort> of <SystemAudioModeRequest>
+                if (cmd.getOpcode() != HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE
+                        || !HdmiUtils.checkCommandSource(cmd, mAvrLogicalAddress, TAG)) {
+                    return false;
+                }
+                boolean receivedStatus = HdmiUtils.parseCommandParamSystemAudioStatus(cmd);
+                if (receivedStatus == mTargetAudioStatus) {
+                    setSystemAudioMode(receivedStatus);
+                    sendGiveAudioStatus();
+                } else {
+                    // Unexpected response, consider the request is newly initiated by AVR.
+                    // To return 'false' will initiate new SystemAudioActionFromAvr by the control
+                    // service.
+                    finish();
+                    return false;
+                }
+                return true;
+
+            case STATE_WAIT_FOR_REPORT_AUDIO_STATUS:
+                // TODO: Handle <FeatureAbort> of <GiveAudioStatus>
+                if (cmd.getOpcode() != HdmiCec.MESSAGE_REPORT_AUDIO_STATUS
+                        || !HdmiUtils.checkCommandSource(cmd, mAvrLogicalAddress, TAG)) {
+                    return false;
+                }
+                byte[] params = cmd.getParams();
+                if (params.length > 0) {
+                    boolean mute = (params[0] & 0x80) == 0x80;
+                    int volume = params[0] & 0x7F;
+                    mService.setAudioStatus(mute, volume);
+                    if (mTargetAudioStatus && mute || !mTargetAudioStatus && !mute) {
+                        // Toggle AVR's mute status to match with the system audio status.
+                        sendUserControlPressedAndReleased(HdmiConstants.UI_COMMAND_MUTE);
+                    }
+                }
+                finish();
+                return true;
+        }
+        return false;
+    }
+
+    protected void removeSystemAudioActionInProgress() {
+        mService.removeActionExcept(SystemAudioActionFromTv.class, this);
+        mService.removeActionExcept(SystemAudioActionFromAvr.class, this);
+    }
+
+    @Override
+    final void handleTimerEvent(int state) {
+        if (mState != state) {
+            return;
+        }
+        switch (mState) {
+            case STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE:
+                handleSendSystemAudioModeRequestTimeout();
+                return;
+            case STATE_WAIT_FOR_REPORT_AUDIO_STATUS:
+                handleSendGiveAudioStatusFailure();
+                return;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
new file mode 100644
index 0000000..c5eb44b
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+
+/**
+ * Feature action that handles System Audio initiated by AVR devices.
+ */
+final class SystemAudioActionFromAvr extends SystemAudioAction {
+    /**
+     * Constructor
+     *
+     * @param service {@link HdmiControlService} instance
+     * @param tvAddress logical address of TV device
+     * @param avrAddress logical address of AVR device
+     * @param targetStatus Whether to enable the system audio mode or not
+     * @throw IllegalArugmentException if device type of tvAddress and avrAddress is invalid
+     */
+    SystemAudioActionFromAvr(HdmiControlService service, int tvAddress, int avrAddress,
+            boolean targetStatus) {
+        super(service, tvAddress, avrAddress, targetStatus);
+        HdmiUtils.verifyAddressType(tvAddress, HdmiCec.DEVICE_TV);
+    }
+
+    @Override
+    boolean start() {
+        removeSystemAudioActionInProgress();
+        handleSystemAudioActionFromAvr();
+        return true;
+    }
+
+    private void handleSystemAudioActionFromAvr() {
+        if (mTargetAudioStatus == mService.getSystemAudioMode()) {
+            finish();
+            return;
+        }
+        if (mService.isInPresetInstallationMode()) {
+            sendCommand(HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                    mSourceAddress, mAvrLogicalAddress,
+                    HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE, HdmiConstants.ABORT_REFUSED));
+            mTargetAudioStatus = false;
+            sendSystemAudioModeRequest();
+            return;
+        }
+        // TODO: Stop the action for System Audio Mode initialization if it is running.
+        if (mTargetAudioStatus) {
+            setSystemAudioMode(true);
+            sendGiveAudioStatus();
+        } else {
+            setSystemAudioMode(false);
+            finish();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
new file mode 100644
index 0000000..9994de6
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+
+
+/**
+ * Feature action that handles System Audio initiated by TV devices.
+ */
+final class SystemAudioActionFromTv extends SystemAudioAction {
+    /**
+     * Constructor
+     *
+     * @param service {@link HdmiControlService} instance
+     * @param tvAddress logical address of TV device
+     * @param avrAddress logical address of AVR device
+     * @param targetStatus Whether to enable the system audio mode or not
+     * @throw IllegalArugmentException if device type of tvAddress is invalid
+     */
+    SystemAudioActionFromTv(HdmiControlService service, int tvAddress, int avrAddress,
+            boolean targetStatus) {
+        super(service, tvAddress, avrAddress, targetStatus);
+        HdmiUtils.verifyAddressType(tvAddress, HdmiCec.DEVICE_TV);
+    }
+
+    @Override
+    boolean start() {
+        // TODO: Check HDMI-CEC is enabled.
+        // TODO: Move to the waiting state if currently a routing change is in progress.
+
+        removeSystemAudioActionInProgress();
+        sendSystemAudioModeRequest();
+        return true;
+    }
+}
diff --git a/services/core/java/com/android/server/location/FlpHardwareProvider.java b/services/core/java/com/android/server/location/FlpHardwareProvider.java
index 51ee93b..2699fea 100644
--- a/services/core/java/com/android/server/location/FlpHardwareProvider.java
+++ b/services/core/java/com/android/server/location/FlpHardwareProvider.java
@@ -96,7 +96,7 @@
                 Looper.myLooper());
     }
 
-    public boolean isSupported() {
+    public static boolean isSupported() {
         return nativeIsSupported();
     }
 
diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java
index c6cf68f..c5b6c7b 100644
--- a/services/core/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/core/java/com/android/server/location/GpsLocationProvider.java
@@ -83,6 +83,8 @@
 import java.util.Date;
 import java.util.Map.Entry;
 import java.util.Properties;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 
 /**
  * A GPS implementation of LocationProvider used by LocationManager.
@@ -158,6 +160,12 @@
     private static final int AGPS_TYPE_SUPL = 1;
     private static final int AGPS_TYPE_C2K = 2;
 
+    // these must match the definitions in gps.h
+    private static final int APN_INVALID = 0;
+    private static final int APN_IPV4 = 1;
+    private static final int APN_IPV6 = 2;
+    private static final int APN_IPV4V6 = 3;
+
     // for mAGpsDataConnectionState
     private static final int AGPS_DATA_CONNECTION_CLOSED = 0;
     private static final int AGPS_DATA_CONNECTION_OPENING = 1;
@@ -312,8 +320,9 @@
     private Handler mHandler;
 
     private String mAGpsApn;
+    private int mApnIpType;
     private int mAGpsDataConnectionState;
-    private int mAGpsDataConnectionIpAddr;
+    private InetAddress mAGpsDataConnectionIpAddr;
     private final ConnectivityManager mConnMgr;
     private final GpsNetInitiatedHandler mNIHandler;
 
@@ -595,28 +604,28 @@
 
         if (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE_SUPL
                 && mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) {
-            String apnName = info.getExtraInfo();
             if (mNetworkAvailable) {
+                String apnName = info.getExtraInfo();
                 if (apnName == null) {
                     /* Assign a dummy value in the case of C2K as otherwise we will have a runtime
                     exception in the following call to native_agps_data_conn_open*/
                     apnName = "dummy-apn";
                 }
                 mAGpsApn = apnName;
-                if (DEBUG) Log.d(TAG, "mAGpsDataConnectionIpAddr " + mAGpsDataConnectionIpAddr);
-                if (mAGpsDataConnectionIpAddr != 0xffffffff) {
-                    boolean route_result;
-                    if (DEBUG) Log.d(TAG, "call requestRouteToHost");
-                    route_result = mConnMgr.requestRouteToHost(ConnectivityManager.TYPE_MOBILE_SUPL,
-                        mAGpsDataConnectionIpAddr);
-                    if (route_result == false) Log.d(TAG, "call requestRouteToHost failed");
+                mApnIpType = getApnIpType(apnName);
+                setRouting();
+                if (DEBUG) {
+                    String message = String.format(
+                            "native_agps_data_conn_open: mAgpsApn=%s, mApnIpType=%s",
+                            mAGpsApn, mApnIpType);
+                    Log.d(TAG, message);
                 }
-                if (DEBUG) Log.d(TAG, "call native_agps_data_conn_open");
-                native_agps_data_conn_open(apnName);
+                native_agps_data_conn_open(mAGpsApn, mApnIpType);
                 mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN;
             } else {
-                if (DEBUG) Log.d(TAG, "call native_agps_data_conn_failed");
+                Log.e(TAG, "call native_agps_data_conn_failed, info: " + info);
                 mAGpsApn = null;
+                mApnIpType = APN_INVALID;
                 mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED;
                 native_agps_data_conn_failed();
             }
@@ -1324,7 +1333,7 @@
     /**
      * called from native code to update AGPS status
      */
-    private void reportAGpsStatus(int type, int status, int ipaddr) {
+    private void reportAGpsStatus(int type, int status, byte[] ipaddr) {
         switch (status) {
             case GPS_REQUEST_AGPS_DATA_CONN:
                 if (DEBUG) Log.d(TAG, "GPS_REQUEST_AGPS_DATA_CONN");
@@ -1333,20 +1342,20 @@
                 mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPENING;
                 int result = mConnMgr.startUsingNetworkFeature(
                         ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL);
-                mAGpsDataConnectionIpAddr = ipaddr;
+                if (ipaddr != null) {
+                    try {
+                        mAGpsDataConnectionIpAddr = InetAddress.getByAddress(ipaddr);
+                    } catch (UnknownHostException e) {
+                        Log.e(TAG, "Bad IP Address: " + ipaddr, e);
+                        mAGpsDataConnectionIpAddr = null;
+                    }
+                }
+
                 if (result == PhoneConstants.APN_ALREADY_ACTIVE) {
                     if (DEBUG) Log.d(TAG, "PhoneConstants.APN_ALREADY_ACTIVE");
                     if (mAGpsApn != null) {
-                        Log.d(TAG, "mAGpsDataConnectionIpAddr " + mAGpsDataConnectionIpAddr);
-                        if (mAGpsDataConnectionIpAddr != 0xffffffff) {
-                            boolean route_result;
-                            if (DEBUG) Log.d(TAG, "call requestRouteToHost");
-                            route_result = mConnMgr.requestRouteToHost(
-                                ConnectivityManager.TYPE_MOBILE_SUPL,
-                                mAGpsDataConnectionIpAddr);
-                            if (route_result == false) Log.d(TAG, "call requestRouteToHost failed");
-                        }
-                        native_agps_data_conn_open(mAGpsApn);
+                        setRouting();
+                        native_agps_data_conn_open(mAGpsApn, mApnIpType);
                         mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN;
                     } else {
                         Log.e(TAG, "mAGpsApn not set when receiving PhoneConstants.APN_ALREADY_ACTIVE");
@@ -1370,6 +1379,7 @@
                             ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL);
                     native_agps_data_conn_closed();
                     mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED;
+                    mAGpsDataConnectionIpAddr = null;
                 }
                 break;
             case GPS_AGPS_DATA_CONNECTED:
@@ -1821,21 +1831,97 @@
 
     private String getSelectedApn() {
         Uri uri = Uri.parse("content://telephony/carriers/preferapn");
-        String apn = null;
-
-        Cursor cursor = mContext.getContentResolver().query(uri, new String[] {"apn"},
-                null, null, Carriers.DEFAULT_SORT_ORDER);
-
-        if (null != cursor) {
-            try {
-                if (cursor.moveToFirst()) {
-                    apn = cursor.getString(0);
-                }
-            } finally {
+        Cursor cursor = null;
+        try {
+            cursor = mContext.getContentResolver().query(
+                    uri,
+                    new String[] { "apn" },
+                    null /* selection */,
+                    null /* selectionArgs */,
+                    Carriers.DEFAULT_SORT_ORDER);
+            if (cursor != null && cursor.moveToFirst()) {
+                return cursor.getString(0);
+            } else {
+                Log.e(TAG, "No APN found to select.");
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Error encountered on selectiong the APN.", e);
+        } finally {
+            if (cursor != null) {
                 cursor.close();
             }
         }
-        return apn;
+
+        return null;
+    }
+
+    private int getApnIpType(String apn) {
+        if (apn == null) {
+            return APN_INVALID;
+        }
+
+        // look for cached data to use
+        if (apn.equals(mAGpsApn) && mApnIpType != APN_INVALID) {
+            return mApnIpType;
+        }
+
+        String selection = String.format("current = 1 and apn = '%s' and carrier_enabled = 1", apn);
+        Cursor cursor = null;
+        try {
+            cursor = mContext.getContentResolver().query(
+                    Carriers.CONTENT_URI,
+                    new String[] { Carriers.PROTOCOL },
+                    selection,
+                    null,
+                    Carriers.DEFAULT_SORT_ORDER);
+
+            if (null != cursor && cursor.moveToFirst()) {
+                return translateToApnIpType(cursor.getString(0), apn);
+            } else {
+                Log.e(TAG, "No entry found in query for APN: " + apn);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Error encountered on APN query for: " + apn, e);
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        return APN_INVALID;
+    }
+
+    private int translateToApnIpType(String ipProtocol, String apn) {
+        if ("IP".equals(ipProtocol)) {
+            return APN_IPV4;
+        }
+        if ("IPV6".equals(ipProtocol)) {
+            return APN_IPV6;
+        }
+        if ("IPV4V6".equals(ipProtocol)) {
+            return APN_IPV4V6;
+        }
+
+        // we hit the default case so the ipProtocol is not recognized
+        String message = String.format("Unknown IP Protocol: %s, for APN: %s", ipProtocol, apn);
+        Log.e(TAG, message);
+        return APN_INVALID;
+    }
+
+    private void setRouting() {
+        if (mAGpsDataConnectionIpAddr == null) {
+            return;
+        }
+
+        boolean result = mConnMgr.requestRouteToHostAddress(
+                ConnectivityManager.TYPE_MOBILE_SUPL,
+                mAGpsDataConnectionIpAddr);
+
+        if (!result) {
+            Log.e(TAG, "Error requesting route to host: " + mAGpsDataConnectionIpAddr);
+        } else if (DEBUG) {
+            Log.d(TAG, "Successfully requested route to host: " + mAGpsDataConnectionIpAddr);
+        }
     }
 
     @Override
@@ -1897,7 +1983,7 @@
     private native String native_get_internal_state();
 
     // AGPS Support
-    private native void native_agps_data_conn_open(String apn);
+    private native void native_agps_data_conn_open(String apn, int apnIpType);
     private native void native_agps_data_conn_closed();
     private native void native_agps_data_conn_failed();
     private native void native_agps_ni_message(byte [] msg, int length);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 386402b..06732ca 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -188,6 +188,8 @@
 
     private AppOpsManager mAppOps;
 
+    private Archive mArchive;
+
     // Notification control database. For now just contains disabled packages.
     private AtomicFile mPolicyFile;
     private HashSet<String> mBlockedPackages = new HashSet<String>();
@@ -223,10 +225,12 @@
     private static final int REASON_LISTENER_CANCEL_ALL = 11;
 
     private static class Archive {
-        static final int BUFFER_SIZE = 250;
-        ArrayDeque<StatusBarNotification> mBuffer = new ArrayDeque<StatusBarNotification>(BUFFER_SIZE);
+        final int mBufferSize;
+        final ArrayDeque<StatusBarNotification> mBuffer;
 
-        public Archive() {
+        public Archive(int size) {
+            mBufferSize = size;
+            mBuffer = new ArrayDeque<StatusBarNotification>(mBufferSize);
         }
 
         public String toString() {
@@ -240,7 +244,7 @@
         }
 
         public void record(StatusBarNotification nr) {
-            if (mBuffer.size() == BUFFER_SIZE) {
+            if (mBuffer.size() == mBufferSize) {
                 mBuffer.removeFirst();
             }
 
@@ -250,7 +254,6 @@
             mBuffer.addLast(nr.cloneLight());
         }
 
-
         public void clear() {
             mBuffer.clear();
         }
@@ -300,7 +303,7 @@
         }
 
         public StatusBarNotification[] getArray(int count) {
-            if (count == 0) count = Archive.BUFFER_SIZE;
+            if (count == 0) count = mBufferSize;
             final StatusBarNotification[] a
                     = new StatusBarNotification[Math.min(count, mBuffer.size())];
             Iterator<StatusBarNotification> iter = descendingIterator();
@@ -312,7 +315,7 @@
         }
 
         public StatusBarNotification[] getArray(int count, String pkg, int userId) {
-            if (count == 0) count = Archive.BUFFER_SIZE;
+            if (count == 0) count = mBufferSize;
             final StatusBarNotification[] a
                     = new StatusBarNotification[Math.min(count, mBuffer.size())];
             Iterator<StatusBarNotification> iter = filter(descendingIterator(), pkg, userId);
@@ -325,8 +328,6 @@
 
     }
 
-    Archive mArchive = new Archive();
-
     private void loadPolicyFile() {
         synchronized(mPolicyFile) {
             mBlockedPackages.clear();
@@ -854,6 +855,9 @@
             }
         }
 
+        mArchive = new Archive(resources.getInteger(
+                R.integer.config_notificationServiceArchiveSize));
+
         publishBinderService(Context.NOTIFICATION_SERVICE, mService);
         publishLocalService(NotificationManagerInternal.class, mInternalService);
     }
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index bab4895..5081bf7 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -44,7 +44,7 @@
  * {@hide}
  */
 public class NotificationUsageStats {
-    private static final boolean ENABLE_SQLITE_LOG = false;
+    private static final boolean ENABLE_SQLITE_LOG = true;
 
     // Guarded by synchronized(this).
     private final Map<String, AggregatedStats> mStats = new HashMap<String, AggregatedStats>();
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index 02f95e9..4ac2dcc 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -34,6 +34,8 @@
 /**
  * This {@link NotificationSignalExtractor} attempts to validate
  * people references. Also elevates the priority of real people.
+ *
+ * {@hide}
  */
 public class ValidateNotificationPeople implements NotificationSignalExtractor {
     private static final String TAG = "ValidateNotificationPeople";
@@ -147,7 +149,8 @@
         };
     }
 
-    private String[] getExtraPeople(Bundle extras) {
+    // VisibleForTesting
+    public static String[] getExtraPeople(Bundle extras) {
         Object people = extras.get(Notification.EXTRA_PEOPLE);
         if (people instanceof String[]) {
             return (String[]) people;
diff --git a/services/core/java/com/android/server/pm/ForwardingIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
similarity index 70%
rename from services/core/java/com/android/server/pm/ForwardingIntentFilter.java
rename to services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
index 85bde98..3d432dc 100644
--- a/services/core/java/com/android/server/pm/ForwardingIntentFilter.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
@@ -26,44 +26,48 @@
 import android.os.UserHandle;
 
 /**
- * The {@link PackageManagerService} maintains some {@link ForwardingIntentFilter}s for every user.
- * If an {@link Intent} matches the {@link ForwardingIntentFilter}, then it can be forwarded to the
- * {@link #mUserIdDest}.
+ * The {@link PackageManagerService} maintains some {@link CrossProfileIntentFilter}s for each user.
+ * If an {@link Intent} matches the {@link CrossProfileIntentFilter}, then activities in the user
+ * {@link #mTargetUserId} can access it.
  */
-class ForwardingIntentFilter extends IntentFilter {
-    private static final String ATTR_USER_ID_DEST = "userIdDest";
+class CrossProfileIntentFilter extends IntentFilter {
+    private static final String ATTR_TARGET_USER_ID = "targetUserId";
+    private static final String ATTR_USER_ID_DEST = "userIdDest";//Old name. Kept for compatibility.
     private static final String ATTR_REMOVABLE = "removable";
     private static final String ATTR_FILTER = "filter";
 
-    private static final String TAG = "ForwardingIntentFilter";
+    private static final String TAG = "CrossProfileIntentFilter";
 
     // If the intent matches the IntentFilter, then it can be forwarded to this userId.
-    final int mUserIdDest;
+    final int mTargetUserId;
     boolean mRemovable;
 
-    ForwardingIntentFilter(IntentFilter filter, boolean removable, int userIdDest) {
+    CrossProfileIntentFilter(IntentFilter filter, boolean removable, int targetUserId) {
         super(filter);
-        mUserIdDest = userIdDest;
+        mTargetUserId = targetUserId;
         mRemovable = removable;
     }
 
-    public int getUserIdDest() {
-        return mUserIdDest;
+    public int getTargetUserId() {
+        return mTargetUserId;
     }
 
     public boolean isRemovable() {
         return mRemovable;
     }
 
-    ForwardingIntentFilter(XmlPullParser parser) throws XmlPullParserException, IOException {
-        String userIdDestString = parser.getAttributeValue(null, ATTR_USER_ID_DEST);
-        if (userIdDestString == null) {
-            String msg = "Missing element under " + TAG +": " + ATTR_USER_ID_DEST + " at " +
+    CrossProfileIntentFilter(XmlPullParser parser) throws XmlPullParserException, IOException {
+        String targetUserIdString = parser.getAttributeValue(null, ATTR_TARGET_USER_ID);
+        if (targetUserIdString == null) {
+            targetUserIdString = parser.getAttributeValue(null, ATTR_USER_ID_DEST);
+        }
+        if (targetUserIdString == null) {
+            String msg = "Missing element under " + TAG +": " + ATTR_TARGET_USER_ID + " at " +
                     parser.getPositionDescription();
             PackageManagerService.reportSettingsProblem(Log.WARN, msg);
-            mUserIdDest = UserHandle.USER_NULL;
+            mTargetUserId = UserHandle.USER_NULL;
         } else {
-            mUserIdDest = Integer.parseInt(userIdDestString);
+            mTargetUserId = Integer.parseInt(targetUserIdString);
         }
         String removableString = parser.getAttributeValue(null, ATTR_REMOVABLE);
         if (removableString != null) {
@@ -99,7 +103,7 @@
     }
 
     public void writeToXml(XmlSerializer serializer) throws IOException {
-        serializer.attribute(null, ATTR_USER_ID_DEST, Integer.toString(mUserIdDest));
+        serializer.attribute(null, ATTR_TARGET_USER_ID, Integer.toString(mTargetUserId));
         serializer.attribute(null, ATTR_REMOVABLE, Boolean.toString(mRemovable));
         serializer.startTag(null, ATTR_FILTER);
             super.writeToXml(serializer);
@@ -108,7 +112,7 @@
 
     @Override
     public String toString() {
-        return "ForwardingIntentFilter{0x" + Integer.toHexString(System.identityHashCode(this))
-                + " " + Integer.toString(mUserIdDest) + "}";
+        return "CrossProfileIntentFilter{0x" + Integer.toHexString(System.identityHashCode(this))
+                + " " + Integer.toString(mTargetUserId) + "}";
     }
 }
diff --git a/services/core/java/com/android/server/pm/ForwardingIntentResolver.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
similarity index 64%
rename from services/core/java/com/android/server/pm/ForwardingIntentResolver.java
rename to services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
index 1616395..a335d3a 100644
--- a/services/core/java/com/android/server/pm/ForwardingIntentResolver.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
@@ -23,22 +23,22 @@
 import java.util.List;
 
 /**
- * Used to find a list of {@link ForwardingIntentFilter}s that match an intent.
+ * Used to find a list of {@link CrossProfileIntentFilter}s that match an intent.
  */
-class ForwardingIntentResolver
-        extends IntentResolver<ForwardingIntentFilter, ForwardingIntentFilter> {
+class CrossProfileIntentResolver
+        extends IntentResolver<CrossProfileIntentFilter, CrossProfileIntentFilter> {
     @Override
-    protected ForwardingIntentFilter[] newArray(int size) {
-        return new ForwardingIntentFilter[size];
+    protected CrossProfileIntentFilter[] newArray(int size) {
+        return new CrossProfileIntentFilter[size];
     }
 
     @Override
-    protected boolean isPackageForFilter(String packageName, ForwardingIntentFilter filter) {
+    protected boolean isPackageForFilter(String packageName, CrossProfileIntentFilter filter) {
         return false;
     }
 
     @Override
-    protected void sortResults(List<ForwardingIntentFilter> results) {
+    protected void sortResults(List<CrossProfileIntentFilter> results) {
         //We don't sort the results
     }
 }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 5e3325c..25ebfc0 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -40,6 +40,7 @@
 import android.util.Slog;
 
 import com.android.internal.content.PackageMonitor;
+import com.android.server.SystemService;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -48,358 +49,374 @@
  * Service that manages requests and callbacks for launchers that support
  * managed profiles. 
  */
-public class LauncherAppsService extends ILauncherApps.Stub {
-    private static final boolean DEBUG = false;
-    private static final String TAG = "LauncherAppsService";
-    private final Context mContext;
-    private final PackageManager mPm;
-    private final UserManager mUm;
-    private final PackageCallbackList<IOnAppsChangedListener> mListeners
-            = new PackageCallbackList<IOnAppsChangedListener>();
 
-    private MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
+public class LauncherAppsService extends SystemService {
+
+    private final LauncherAppsImpl mLauncherAppsImpl;
 
     public LauncherAppsService(Context context) {
-        mContext = context;
-        mPm = mContext.getPackageManager();
-        mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        super(context);
+        mLauncherAppsImpl = new LauncherAppsImpl(context);
     }
 
-    /*
-     * @see android.content.pm.ILauncherApps#addOnAppsChangedListener(
-     *          android.content.pm.IOnAppsChangedListener)
-     */
     @Override
-    public void addOnAppsChangedListener(IOnAppsChangedListener listener) throws RemoteException {
-        synchronized (mListeners) {
-            if (DEBUG) {
-                Log.d(TAG, "Adding listener from " + Binder.getCallingUserHandle());
-            }
-            if (mListeners.getRegisteredCallbackCount() == 0) {
+    public void onStart() {
+        publishBinderService(Context.LAUNCHER_APPS_SERVICE, mLauncherAppsImpl);
+    }
+
+    class LauncherAppsImpl extends ILauncherApps.Stub {
+        private static final boolean DEBUG = false;
+        private static final String TAG = "LauncherAppsService";
+        private final Context mContext;
+        private final PackageManager mPm;
+        private final UserManager mUm;
+        private final PackageCallbackList<IOnAppsChangedListener> mListeners
+                = new PackageCallbackList<IOnAppsChangedListener>();
+
+        private MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
+
+        public LauncherAppsImpl(Context context) {
+            mContext = context;
+            mPm = mContext.getPackageManager();
+            mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        }
+
+        /*
+         * @see android.content.pm.ILauncherApps#addOnAppsChangedListener(
+         *          android.content.pm.IOnAppsChangedListener)
+         */
+        @Override
+        public void addOnAppsChangedListener(IOnAppsChangedListener listener) throws RemoteException {
+            synchronized (mListeners) {
                 if (DEBUG) {
-                    Log.d(TAG, "Starting package monitoring");
+                    Log.d(TAG, "Adding listener from " + Binder.getCallingUserHandle());
                 }
-                startWatchingPackageBroadcasts();
+                if (mListeners.getRegisteredCallbackCount() == 0) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Starting package monitoring");
+                    }
+                    startWatchingPackageBroadcasts();
+                }
+                mListeners.unregister(listener);
+                mListeners.register(listener, Binder.getCallingUserHandle());
             }
-            mListeners.unregister(listener);
-            mListeners.register(listener, Binder.getCallingUserHandle());
         }
-    }
 
-    /*
-     * @see android.content.pm.ILauncherApps#removeOnAppsChangedListener(
-     *          android.content.pm.IOnAppsChangedListener)
-     */
-    @Override
-    public void removeOnAppsChangedListener(IOnAppsChangedListener listener)
-            throws RemoteException {
-        synchronized (mListeners) {
+        /*
+         * @see android.content.pm.ILauncherApps#removeOnAppsChangedListener(
+         *          android.content.pm.IOnAppsChangedListener)
+         */
+        @Override
+        public void removeOnAppsChangedListener(IOnAppsChangedListener listener)
+                throws RemoteException {
+            synchronized (mListeners) {
+                if (DEBUG) {
+                    Log.d(TAG, "Removing listener from " + Binder.getCallingUserHandle());
+                }
+                mListeners.unregister(listener);
+                if (mListeners.getRegisteredCallbackCount() == 0) {
+                    stopWatchingPackageBroadcasts();
+                }
+            }
+        }
+
+        /**
+         * Register a receiver to watch for package broadcasts
+         */
+        private void startWatchingPackageBroadcasts() {
+            mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
+        }
+
+        /**
+         * Unregister package broadcast receiver
+         */
+        private void stopWatchingPackageBroadcasts() {
             if (DEBUG) {
-                Log.d(TAG, "Removing listener from " + Binder.getCallingUserHandle());
+                Log.d(TAG, "Stopped watching for packages");
             }
-            mListeners.unregister(listener);
-            if (mListeners.getRegisteredCallbackCount() == 0) {
-                stopWatchingPackageBroadcasts();
+            mPackageMonitor.unregister();
+        }
+
+        void checkCallbackCount() {
+            synchronized (mListeners) {
+                if (DEBUG) {
+                    Log.d(TAG, "Callback count = " + mListeners.getRegisteredCallbackCount());
+                }
+                if (mListeners.getRegisteredCallbackCount() == 0) {
+                    stopWatchingPackageBroadcasts();
+                }
             }
         }
-    }
 
-    /**
-     * Register a receiver to watch for package broadcasts
-     */
-    private void startWatchingPackageBroadcasts() {
-        mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
-    }
+        /**
+         * Checks if the caller is in the same group as the userToCheck.
+         */
+        private void ensureInUserProfiles(UserHandle userToCheck, String message) {
+            final int callingUserId = UserHandle.getCallingUserId();
+            final int targetUserId = userToCheck.getIdentifier();
 
-    /**
-     * Unregister package broadcast receiver
-     */
-    private void stopWatchingPackageBroadcasts() {
-        if (DEBUG) {
-            Log.d(TAG, "Stopped watching for packages");
-        }
-        mPackageMonitor.unregister();
-    }
+            if (targetUserId == callingUserId) return;
 
-    void checkCallbackCount() {
-        synchronized (mListeners) {
-            if (DEBUG) {
-                Log.d(TAG, "Callback count = " + mListeners.getRegisteredCallbackCount());
-            }
-            if (mListeners.getRegisteredCallbackCount() == 0) {
-                stopWatchingPackageBroadcasts();
-            }
-        }
-    }
-
-    /**
-     * Checks if the caller is in the same group as the userToCheck.
-     */
-    private void ensureInUserProfiles(UserHandle userToCheck, String message) {
-        final int callingUserId = UserHandle.getCallingUserId();
-        final int targetUserId = userToCheck.getIdentifier();
-
-        if (targetUserId == callingUserId) return;
-
-        long ident = Binder.clearCallingIdentity();
-        try {
-            UserInfo callingUserInfo = mUm.getUserInfo(callingUserId);
-            UserInfo targetUserInfo = mUm.getUserInfo(targetUserId);
-            if (targetUserInfo == null
-                    || targetUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID
-                    || targetUserInfo.profileGroupId != callingUserInfo.profileGroupId) {
-                throw new SecurityException(message);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    /**
-     * Checks if the user is enabled.
-     */
-    private boolean isUserEnabled(UserHandle user) {
-        long ident = Binder.clearCallingIdentity();
-        try {
-            UserInfo targetUserInfo = mUm.getUserInfo(user.getIdentifier());
-            return targetUserInfo != null && targetUserInfo.isEnabled();
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    @Override
-    public List<ResolveInfo> getLauncherActivities(String packageName, UserHandle user)
-            throws RemoteException {
-        ensureInUserProfiles(user, "Cannot retrieve activities for unrelated profile " + user);
-        if (!isUserEnabled(user)) {
-            return new ArrayList<ResolveInfo>();
-        }
-
-        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
-        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-        mainIntent.setPackage(packageName);
-        long ident = Binder.clearCallingIdentity();
-        try {
-            List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent, 0,
-                    user.getIdentifier());
-            return apps;
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    @Override
-    public ResolveInfo resolveActivity(Intent intent, UserHandle user)
-            throws RemoteException {
-        ensureInUserProfiles(user, "Cannot resolve activity for unrelated profile " + user);
-        if (!isUserEnabled(user)) {
-            return null;
-        }
-
-        long ident = Binder.clearCallingIdentity();
-        try {
-            ResolveInfo app = mPm.resolveActivityAsUser(intent, 0, user.getIdentifier());
-            return app;
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    @Override
-    public boolean isPackageEnabled(String packageName, UserHandle user)
-            throws RemoteException {
-        ensureInUserProfiles(user, "Cannot check package for unrelated profile " + user);
-        if (!isUserEnabled(user)) {
-            return false;
-        }
-
-        long ident = Binder.clearCallingIdentity();
-        try {
-            IPackageManager pm = AppGlobals.getPackageManager();
-            PackageInfo info = pm.getPackageInfo(packageName, 0, user.getIdentifier());
-            return info != null && info.applicationInfo.enabled;
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    @Override
-    public boolean isActivityEnabled(ComponentName component, UserHandle user)
-            throws RemoteException {
-        ensureInUserProfiles(user, "Cannot check component for unrelated profile " + user);
-        if (!isUserEnabled(user)) {
-            return false;
-        }
-
-        long ident = Binder.clearCallingIdentity();
-        try {
-            IPackageManager pm = AppGlobals.getPackageManager();
-            ActivityInfo info = pm.getActivityInfo(component, 0, user.getIdentifier());
-            return info != null && info.isEnabled();
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    @Override
-    public void startActivityAsUser(ComponentName component, Rect sourceBounds,
-            Bundle opts, UserHandle user) throws RemoteException {
-        ensureInUserProfiles(user, "Cannot start activity for unrelated profile " + user);
-        if (!isUserEnabled(user)) {
-            throw new IllegalStateException("Cannot start activity for disabled profile "  + user);
-        }
-
-        Intent launchIntent = new Intent(Intent.ACTION_MAIN);
-        launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-        launchIntent.setComponent(component);
-        launchIntent.setSourceBounds(sourceBounds);
-        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        long ident = Binder.clearCallingIdentity();
-        try {
-            mContext.startActivityAsUser(launchIntent, opts, user);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    private class MyPackageMonitor extends PackageMonitor {
-
-        /** Checks if user is a profile of or same as listeningUser.
-          * and the user is enabled. */
-        private boolean isEnabledProfileOf(UserHandle user, UserHandle listeningUser,
-                String debugMsg) {
-            if (user.getIdentifier() == listeningUser.getIdentifier()) {
-                if (DEBUG) Log.d(TAG, "Delivering msg to same user " + debugMsg);
-                return true;
-            }
             long ident = Binder.clearCallingIdentity();
             try {
-                UserInfo userInfo = mUm.getUserInfo(user.getIdentifier());
-                UserInfo listeningUserInfo = mUm.getUserInfo(listeningUser.getIdentifier());
-                if (userInfo == null || listeningUserInfo == null
-                        || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID
-                        || userInfo.profileGroupId != listeningUserInfo.profileGroupId
-                        || !userInfo.isEnabled()) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Not delivering msg from " + user + " to " + listeningUser + ":"
-                                + debugMsg);
-                    }
-                    return false;
-                } else {
-                    if (DEBUG) {
-                        Log.d(TAG, "Delivering msg from " + user + " to " + listeningUser + ":"
-                                + debugMsg);
-                    }
-                    return true;
+                UserInfo callingUserInfo = mUm.getUserInfo(callingUserId);
+                UserInfo targetUserInfo = mUm.getUserInfo(targetUserId);
+                if (targetUserInfo == null
+                        || targetUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID
+                        || targetUserInfo.profileGroupId != callingUserInfo.profileGroupId) {
+                    throw new SecurityException(message);
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
         }
 
-        @Override
-        public void onPackageAdded(String packageName, int uid) {
-            UserHandle user = new UserHandle(getChangingUserId());
-            final int n = mListeners.beginBroadcast();
-            for (int i = 0; i < n; i++) {
-                IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
-                UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
-                if (!isEnabledProfileOf(user, listeningUser, "onPackageAdded")) continue;
-                try {
-                    listener.onPackageAdded(user, packageName);
-                } catch (RemoteException re) {
-                    Slog.d(TAG, "Callback failed ", re);
-                }
+        /**
+         * Checks if the user is enabled.
+         */
+        private boolean isUserEnabled(UserHandle user) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                UserInfo targetUserInfo = mUm.getUserInfo(user.getIdentifier());
+                return targetUserInfo != null && targetUserInfo.isEnabled();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
             }
-            mListeners.finishBroadcast();
-
-            super.onPackageAdded(packageName, uid);
         }
 
         @Override
-        public void onPackageRemoved(String packageName, int uid) {
-            UserHandle user = new UserHandle(getChangingUserId());
-            final int n = mListeners.beginBroadcast();
-            for (int i = 0; i < n; i++) {
-                IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
-                UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
-                if (!isEnabledProfileOf(user, listeningUser, "onPackageRemoved")) continue;
-                try {
-                    listener.onPackageRemoved(user, packageName);
-                } catch (RemoteException re) {
-                    Slog.d(TAG, "Callback failed ", re);
-                }
+        public List<ResolveInfo> getLauncherActivities(String packageName, UserHandle user)
+                throws RemoteException {
+            ensureInUserProfiles(user, "Cannot retrieve activities for unrelated profile " + user);
+            if (!isUserEnabled(user)) {
+                return new ArrayList<ResolveInfo>();
             }
-            mListeners.finishBroadcast();
 
-            super.onPackageRemoved(packageName, uid);
+            final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+            mainIntent.setPackage(packageName);
+            long ident = Binder.clearCallingIdentity();
+            try {
+                List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent, 0,
+                        user.getIdentifier());
+                return apps;
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
         }
 
         @Override
-        public void onPackageModified(String packageName) {
-            UserHandle user = new UserHandle(getChangingUserId());
-            final int n = mListeners.beginBroadcast();
-            for (int i = 0; i < n; i++) {
-                IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
-                UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
-                if (!isEnabledProfileOf(user, listeningUser, "onPackageModified")) continue;
-                try {
-                    listener.onPackageChanged(user, packageName);
-                } catch (RemoteException re) {
-                    Slog.d(TAG, "Callback failed ", re);
-                }
+        public ResolveInfo resolveActivity(Intent intent, UserHandle user)
+                throws RemoteException {
+            ensureInUserProfiles(user, "Cannot resolve activity for unrelated profile " + user);
+            if (!isUserEnabled(user)) {
+                return null;
             }
-            mListeners.finishBroadcast();
 
-            super.onPackageModified(packageName);
+            long ident = Binder.clearCallingIdentity();
+            try {
+                ResolveInfo app = mPm.resolveActivityAsUser(intent, 0, user.getIdentifier());
+                return app;
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
         }
 
         @Override
-        public void onPackagesAvailable(String[] packages) {
-            UserHandle user = new UserHandle(getChangingUserId());
-            final int n = mListeners.beginBroadcast();
-            for (int i = 0; i < n; i++) {
-                IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
-                UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
-                if (!isEnabledProfileOf(user, listeningUser, "onPackagesAvailable")) continue;
-                try {
-                    listener.onPackagesAvailable(user, packages, isReplacing());
-                } catch (RemoteException re) {
-                    Slog.d(TAG, "Callback failed ", re);
-                }
+        public boolean isPackageEnabled(String packageName, UserHandle user)
+                throws RemoteException {
+            ensureInUserProfiles(user, "Cannot check package for unrelated profile " + user);
+            if (!isUserEnabled(user)) {
+                return false;
             }
-            mListeners.finishBroadcast();
 
-            super.onPackagesAvailable(packages);
+            long ident = Binder.clearCallingIdentity();
+            try {
+                IPackageManager pm = AppGlobals.getPackageManager();
+                PackageInfo info = pm.getPackageInfo(packageName, 0, user.getIdentifier());
+                return info != null && info.applicationInfo.enabled;
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
         }
 
         @Override
-        public void onPackagesUnavailable(String[] packages) {
-            UserHandle user = new UserHandle(getChangingUserId());
-            final int n = mListeners.beginBroadcast();
-            for (int i = 0; i < n; i++) {
-                IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
-                UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
-                if (!isEnabledProfileOf(user, listeningUser, "onPackagesUnavailable")) continue;
-                try {
-                    listener.onPackagesUnavailable(user, packages, isReplacing());
-                } catch (RemoteException re) {
-                    Slog.d(TAG, "Callback failed ", re);
-                }
+        public boolean isActivityEnabled(ComponentName component, UserHandle user)
+                throws RemoteException {
+            ensureInUserProfiles(user, "Cannot check component for unrelated profile " + user);
+            if (!isUserEnabled(user)) {
+                return false;
             }
-            mListeners.finishBroadcast();
 
-            super.onPackagesUnavailable(packages);
+            long ident = Binder.clearCallingIdentity();
+            try {
+                IPackageManager pm = AppGlobals.getPackageManager();
+                ActivityInfo info = pm.getActivityInfo(component, 0, user.getIdentifier());
+                return info != null && info.isEnabled();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
         }
 
-    }
-
-    class PackageCallbackList<T extends IInterface> extends RemoteCallbackList<T> {
         @Override
-        public void onCallbackDied(T callback, Object cookie) {
-            checkCallbackCount();
+        public void startActivityAsUser(ComponentName component, Rect sourceBounds,
+                Bundle opts, UserHandle user) throws RemoteException {
+            ensureInUserProfiles(user, "Cannot start activity for unrelated profile " + user);
+            if (!isUserEnabled(user)) {
+                throw new IllegalStateException("Cannot start activity for disabled profile "  + user);
+            }
+
+            Intent launchIntent = new Intent(Intent.ACTION_MAIN);
+            launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+            launchIntent.setComponent(component);
+            launchIntent.setSourceBounds(sourceBounds);
+            launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+            long ident = Binder.clearCallingIdentity();
+            try {
+                mContext.startActivityAsUser(launchIntent, opts, user);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        private class MyPackageMonitor extends PackageMonitor {
+
+            /** Checks if user is a profile of or same as listeningUser.
+              * and the user is enabled. */
+            private boolean isEnabledProfileOf(UserHandle user, UserHandle listeningUser,
+                    String debugMsg) {
+                if (user.getIdentifier() == listeningUser.getIdentifier()) {
+                    if (DEBUG) Log.d(TAG, "Delivering msg to same user " + debugMsg);
+                    return true;
+                }
+                long ident = Binder.clearCallingIdentity();
+                try {
+                    UserInfo userInfo = mUm.getUserInfo(user.getIdentifier());
+                    UserInfo listeningUserInfo = mUm.getUserInfo(listeningUser.getIdentifier());
+                    if (userInfo == null || listeningUserInfo == null
+                            || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID
+                            || userInfo.profileGroupId != listeningUserInfo.profileGroupId
+                            || !userInfo.isEnabled()) {
+                        if (DEBUG) {
+                            Log.d(TAG, "Not delivering msg from " + user + " to " + listeningUser + ":"
+                                    + debugMsg);
+                        }
+                        return false;
+                    } else {
+                        if (DEBUG) {
+                            Log.d(TAG, "Delivering msg from " + user + " to " + listeningUser + ":"
+                                    + debugMsg);
+                        }
+                        return true;
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+
+            @Override
+            public void onPackageAdded(String packageName, int uid) {
+                UserHandle user = new UserHandle(getChangingUserId());
+                final int n = mListeners.beginBroadcast();
+                for (int i = 0; i < n; i++) {
+                    IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+                    UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
+                    if (!isEnabledProfileOf(user, listeningUser, "onPackageAdded")) continue;
+                    try {
+                        listener.onPackageAdded(user, packageName);
+                    } catch (RemoteException re) {
+                        Slog.d(TAG, "Callback failed ", re);
+                    }
+                }
+                mListeners.finishBroadcast();
+
+                super.onPackageAdded(packageName, uid);
+            }
+
+            @Override
+            public void onPackageRemoved(String packageName, int uid) {
+                UserHandle user = new UserHandle(getChangingUserId());
+                final int n = mListeners.beginBroadcast();
+                for (int i = 0; i < n; i++) {
+                    IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+                    UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
+                    if (!isEnabledProfileOf(user, listeningUser, "onPackageRemoved")) continue;
+                    try {
+                        listener.onPackageRemoved(user, packageName);
+                    } catch (RemoteException re) {
+                        Slog.d(TAG, "Callback failed ", re);
+                    }
+                }
+                mListeners.finishBroadcast();
+
+                super.onPackageRemoved(packageName, uid);
+            }
+
+            @Override
+            public void onPackageModified(String packageName) {
+                UserHandle user = new UserHandle(getChangingUserId());
+                final int n = mListeners.beginBroadcast();
+                for (int i = 0; i < n; i++) {
+                    IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+                    UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
+                    if (!isEnabledProfileOf(user, listeningUser, "onPackageModified")) continue;
+                    try {
+                        listener.onPackageChanged(user, packageName);
+                    } catch (RemoteException re) {
+                        Slog.d(TAG, "Callback failed ", re);
+                    }
+                }
+                mListeners.finishBroadcast();
+
+                super.onPackageModified(packageName);
+            }
+
+            @Override
+            public void onPackagesAvailable(String[] packages) {
+                UserHandle user = new UserHandle(getChangingUserId());
+                final int n = mListeners.beginBroadcast();
+                for (int i = 0; i < n; i++) {
+                    IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+                    UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
+                    if (!isEnabledProfileOf(user, listeningUser, "onPackagesAvailable")) continue;
+                    try {
+                        listener.onPackagesAvailable(user, packages, isReplacing());
+                    } catch (RemoteException re) {
+                        Slog.d(TAG, "Callback failed ", re);
+                    }
+                }
+                mListeners.finishBroadcast();
+
+                super.onPackagesAvailable(packages);
+            }
+
+            @Override
+            public void onPackagesUnavailable(String[] packages) {
+                UserHandle user = new UserHandle(getChangingUserId());
+                final int n = mListeners.beginBroadcast();
+                for (int i = 0; i < n; i++) {
+                    IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+                    UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i);
+                    if (!isEnabledProfileOf(user, listeningUser, "onPackagesUnavailable")) continue;
+                    try {
+                        listener.onPackagesUnavailable(user, packages, isReplacing());
+                    } catch (RemoteException re) {
+                        Slog.d(TAG, "Callback failed ", re);
+                    }
+                }
+                mListeners.finishBroadcast();
+
+                super.onPackagesUnavailable(packages);
+            }
+
+        }
+
+        class PackageCallbackList<T extends IInterface> extends RemoteCallbackList<T> {
+            @Override
+            public void onCallbackDied(T callback, Object cookie) {
+                checkCallbackCount();
+            }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index f90d7ab..3ed73f7 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -32,6 +32,7 @@
 import android.content.pm.Signature;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.FileBridge;
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.Looper;
@@ -114,7 +115,7 @@
     private boolean mPermissionsConfirmed;
     private boolean mInvalid;
 
-    private ArrayList<WritePipe> mPipes = new ArrayList<>();
+    private ArrayList<FileBridge> mBridges = new ArrayList<>();
 
     private IPackageInstallObserver2 mRemoteObserver;
 
@@ -159,14 +160,14 @@
         // Quick sanity check of state, and allocate a pipe for ourselves. We
         // then do heavy disk allocation outside the lock, but this open pipe
         // will block any attempted install transitions.
-        final WritePipe pipe;
+        final FileBridge bridge;
         synchronized (mLock) {
             if (!mMutationsAllowed) {
                 throw new IllegalStateException("Mutations not allowed");
             }
 
-            pipe = new WritePipe();
-            mPipes.add(pipe);
+            bridge = new FileBridge();
+            mBridges.add(bridge);
         }
 
         try {
@@ -194,9 +195,9 @@
                 Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
             }
 
-            pipe.setTargetFd(targetFd);
-            pipe.start();
-            return pipe.getWriteFd();
+            bridge.setTargetFile(targetFd);
+            bridge.start();
+            return new ParcelFileDescriptor(bridge.getClientSocket());
 
         } catch (ErrnoException e) {
             throw new IllegalStateException("Failed to write", e);
@@ -218,8 +219,8 @@
 
         // Verify that all writers are hands-off
         if (mMutationsAllowed) {
-            for (WritePipe pipe : mPipes) {
-                if (!pipe.isClosed()) {
+            for (FileBridge bridge : mBridges) {
+                if (!bridge.isClosed()) {
                     throw new InstallFailedException(INSTALL_FAILED_PACKAGE_CHANGED,
                             "Files still open");
                 }
@@ -482,52 +483,6 @@
         }
     }
 
-    private static class WritePipe extends Thread {
-        private final ParcelFileDescriptor[] mPipe;
-
-        private FileDescriptor mTargetFd;
-
-        private volatile boolean mClosed;
-
-        public WritePipe() {
-            try {
-                mPipe = ParcelFileDescriptor.createPipe();
-            } catch (IOException e) {
-                throw new IllegalStateException("Failed to create pipe");
-            }
-        }
-
-        public boolean isClosed() {
-            return mClosed;
-        }
-
-        public void setTargetFd(FileDescriptor targetFd) {
-            mTargetFd = targetFd;
-        }
-
-        public ParcelFileDescriptor getWriteFd() {
-            return mPipe[1];
-        }
-
-        @Override
-        public void run() {
-            FileInputStream in = null;
-            FileOutputStream out = null;
-            try {
-                // TODO: look at switching to sendfile(2) to speed up
-                in = new FileInputStream(mPipe[0].getFileDescriptor());
-                out = new FileOutputStream(mTargetFd);
-                Streams.copy(in, out);
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed to stream data: " + e);
-            } finally {
-                IoUtils.closeQuietly(mPipe[0]);
-                IoUtils.closeQuietly(mTargetFd);
-                mClosed = true;
-            }
-        }
-    }
-
     private class InstallFailedException extends Exception {
         private final int error;
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 407ecb8..49502b6 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -40,6 +40,7 @@
 import com.android.internal.app.IMediaContainerService;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.content.NativeLibraryHelper.ApkHandle;
 import com.android.internal.content.PackageHelper;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FastXmlSerializer;
@@ -3374,25 +3375,26 @@
      * Returns if intent can be forwarded from the userId from to dest
      */
     @Override
-    public boolean canForwardTo(Intent intent, String resolvedType, int userIdFrom, int userIdDest) {
+    public boolean canForwardTo(Intent intent, String resolvedType, int sourceUserId,
+            int targetUserId) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
-        List<ForwardingIntentFilter> matches =
-                getMatchingForwardingIntentFilters(intent, resolvedType, userIdFrom);
+        List<CrossProfileIntentFilter> matches =
+                getMatchingCrossProfileIntentFilters(intent, resolvedType, sourceUserId);
         if (matches != null) {
             int size = matches.size();
             for (int i = 0; i < size; i++) {
-                if (matches.get(i).getUserIdDest() == userIdDest) return true;
+                if (matches.get(i).getTargetUserId() == targetUserId) return true;
             }
         }
         return false;
     }
 
-    private List<ForwardingIntentFilter> getMatchingForwardingIntentFilters(Intent intent,
+    private List<CrossProfileIntentFilter> getMatchingCrossProfileIntentFilters(Intent intent,
             String resolvedType, int userId) {
-        ForwardingIntentResolver fir = mSettings.mForwardingIntentResolvers.get(userId);
-        if (fir != null) {
-            return fir.queryIntent(intent, resolvedType, false, userId);
+        CrossProfileIntentResolver cpir = mSettings.mCrossProfileIntentResolvers.get(userId);
+        if (cpir != null) {
+            return cpir.queryIntent(intent, resolvedType, false, userId);
         }
         return null;
     }
@@ -3428,31 +3430,31 @@
                 List<ResolveInfo> result =
                         mActivities.queryIntent(intent, resolvedType, flags, userId);
                 // Checking if we can forward the intent to another user
-                List<ForwardingIntentFilter> fifs =
-                        getMatchingForwardingIntentFilters(intent, resolvedType, userId);
-                if (fifs != null) {
-                    ForwardingIntentFilter forwardingIntentFilterWithResult = null;
+                List<CrossProfileIntentFilter> cpifs =
+                        getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);
+                if (cpifs != null) {
+                    CrossProfileIntentFilter crossProfileIntentFilterWithResult = null;
                     HashSet<Integer> alreadyTriedUserIds = new HashSet<Integer>();
-                    for (ForwardingIntentFilter fif : fifs) {
-                        int userIdDest = fif.getUserIdDest();
-                        // Two {@link ForwardingIntentFilter}s can have the same userIdDest and
+                    for (CrossProfileIntentFilter cpif : cpifs) {
+                        int targetUserId = cpif.getTargetUserId();
+                        // Two {@link CrossProfileIntentFilter}s can have the same targetUserId and
                         // match the same an intent. For performance reasons, it is better not to
                         // run queryIntent twice for the same userId
-                        if (!alreadyTriedUserIds.contains(userIdDest)) {
+                        if (!alreadyTriedUserIds.contains(targetUserId)) {
                             List<ResolveInfo> resultUser = mActivities.queryIntent(intent,
-                                    resolvedType, flags, userIdDest);
+                                    resolvedType, flags, targetUserId);
                             if (resultUser != null) {
-                                forwardingIntentFilterWithResult = fif;
+                                crossProfileIntentFilterWithResult = cpif;
                                 // As soon as there is a match in another user, we add the
                                 // intentForwarderActivity to the list of ResolveInfo.
                                 break;
                             }
-                            alreadyTriedUserIds.add(userIdDest);
+                            alreadyTriedUserIds.add(targetUserId);
                         }
                     }
-                    if (forwardingIntentFilterWithResult != null) {
+                    if (crossProfileIntentFilterWithResult != null) {
                         ResolveInfo forwardingResolveInfo = createForwardingResolveInfo(
-                                forwardingIntentFilterWithResult, userId);
+                                crossProfileIntentFilterWithResult, userId);
                         result.add(forwardingResolveInfo);
                     }
                 }
@@ -3467,10 +3469,11 @@
         }
     }
 
-    private ResolveInfo createForwardingResolveInfo(ForwardingIntentFilter fif, int userIdFrom) {
+    private ResolveInfo createForwardingResolveInfo(CrossProfileIntentFilter cpif,
+            int sourceUserId) {
         String className;
-        int userIdDest = fif.getUserIdDest();
-        if (userIdDest == UserHandle.USER_OWNER) {
+        int targetUserId = cpif.getTargetUserId();
+        if (targetUserId == UserHandle.USER_OWNER) {
             className = FORWARD_INTENT_TO_USER_OWNER;
         } else {
             className = FORWARD_INTENT_TO_MANAGED_PROFILE;
@@ -3478,14 +3481,14 @@
         ComponentName forwardingActivityComponentName = new ComponentName(
                 mAndroidApplication.packageName, className);
         ActivityInfo forwardingActivityInfo = getActivityInfo(forwardingActivityComponentName, 0,
-                userIdFrom);
+                sourceUserId);
         ResolveInfo forwardingResolveInfo = new ResolveInfo();
         forwardingResolveInfo.activityInfo = forwardingActivityInfo;
         forwardingResolveInfo.priority = 0;
         forwardingResolveInfo.preferredOrder = 0;
         forwardingResolveInfo.match = 0;
         forwardingResolveInfo.isDefault = true;
-        forwardingResolveInfo.filter = fif;
+        forwardingResolveInfo.filter = cpif;
         return forwardingResolveInfo;
     }
 
@@ -4146,7 +4149,7 @@
                 continue;
             }
             PackageParser.Package pkg = scanPackageLI(file,
-                    flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime, null);
+                    flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime, null, null);
             // Don't mess around with apps in system partition.
             if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
                     mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) {
@@ -4213,7 +4216,7 @@
      *  Returns null in case of errors and the error code is stored in mLastScanError
      */
     private PackageParser.Package scanPackageLI(File scanFile,
-            int parseFlags, int scanMode, long currentTime, UserHandle user) {
+            int parseFlags, int scanMode, long currentTime, UserHandle user, String abiOverride) {
         mLastScanError = PackageManager.INSTALL_SUCCEEDED;
         String scanPath = scanFile.getPath();
         if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanPath);
@@ -4281,7 +4284,7 @@
                     mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
                     return null;
                 } else {
-                    // The current app on the system partion is better than
+                    // The current app on the system partition is better than
                     // what we have updated to on the data partition; switch
                     // back to the system partition version.
                     // At this point, its safely assumed that package installation for
@@ -4400,7 +4403,7 @@
         setApplicationInfoPaths(pkg, codePath, resPath);
         // Note that we invoke the following method only if we are about to unpack an application
         PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanMode
-                | SCAN_UPDATE_SIGNATURE, currentTime, user);
+                | SCAN_UPDATE_SIGNATURE, currentTime, user, abiOverride);
 
         /*
          * If the system app should be overridden by a previously installed
@@ -4943,7 +4946,7 @@
     }
 
     private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
-            int parseFlags, int scanMode, long currentTime, UserHandle user) {
+            int parseFlags, int scanMode, long currentTime, UserHandle user, String abiOverride) {
         File scanFile = new File(pkg.mScanPath);
         if (scanFile == null || pkg.applicationInfo.sourceDir == null ||
                 pkg.applicationInfo.publicSourceDir == null) {
@@ -5394,7 +5397,22 @@
          *        only for non-system apps and system app upgrades.
          */
         if (pkg.applicationInfo.nativeLibraryDir != null) {
+            final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(scanFile);
             try {
+                // Enable gross and lame hacks for apps that are built with old
+                // SDK tools. We must scan their APKs for renderscript bitcode and
+                // not launch them if it's present. Don't bother checking on devices
+                // that don't have 64 bit support.
+                String[] abiList = Build.SUPPORTED_ABIS;
+                boolean hasLegacyRenderscriptBitcode = false;
+                if (abiOverride != null) {
+                    abiList = new String[] { abiOverride };
+                } else if (Build.SUPPORTED_64_BIT_ABIS.length > 0 &&
+                        NativeLibraryHelper.hasRenderscriptBitcode(handle)) {
+                    abiList = Build.SUPPORTED_32_BIT_ABIS;
+                    hasLegacyRenderscriptBitcode = true;
+                }
+
                 File nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir);
                 final String dataPathString = dataPath.getCanonicalPath();
 
@@ -5410,21 +5428,26 @@
                         Log.i(TAG, "removed obsolete native libraries for system package "
                                 + path);
                     }
-
-                    setInternalAppAbi(pkg, pkgSetting);
+                    if (abiOverride != null || hasLegacyRenderscriptBitcode) {
+                        pkg.applicationInfo.cpuAbi = abiList[0];
+                        pkgSetting.cpuAbiString = abiList[0];
+                    } else {
+                        setInternalAppAbi(pkg, pkgSetting);
+                    }
                 } else {
                     if (!isForwardLocked(pkg) && !isExternal(pkg)) {
                         /*
-                         * Update native library dir if it starts with
-                         * /data/data
-                         */
+                        * Update native library dir if it starts with
+                        * /data/data
+                        */
                         if (nativeLibraryDir.getPath().startsWith(dataPathString)) {
                             setInternalAppNativeLibraryPath(pkg, pkgSetting);
                             nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir);
                         }
 
                         try {
-                            int copyRet = copyNativeLibrariesForInternalApp(scanFile, nativeLibraryDir);
+                            int copyRet = copyNativeLibrariesForInternalApp(handle,
+                                    nativeLibraryDir, abiList);
                             if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
                                 Slog.e(TAG, "Unable to copy native libraries");
                                 mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
@@ -5434,7 +5457,9 @@
                             // We've successfully copied native libraries across, so we make a
                             // note of what ABI we're using
                             if (copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
-                                pkg.applicationInfo.cpuAbi = Build.SUPPORTED_ABIS[copyRet];
+                                pkg.applicationInfo.cpuAbi = abiList[copyRet];
+                            } else if (abiOverride != null || hasLegacyRenderscriptBitcode) {
+                                pkg.applicationInfo.cpuAbi = abiList[0];
                             } else {
                                 pkg.applicationInfo.cpuAbi = null;
                             }
@@ -5451,20 +5476,22 @@
                         // to clean this up but we'll need to change the interface between this service
                         // and IMediaContainerService (but doing so will spread this logic out, rather
                         // than centralizing it).
-                        final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(scanFile);
-                        final int abi = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_ABIS);
+                        final int abi = NativeLibraryHelper.findSupportedAbi(handle, abiList);
                         if (abi >= 0) {
-                            pkg.applicationInfo.cpuAbi = Build.SUPPORTED_ABIS[abi];
+                            pkg.applicationInfo.cpuAbi = abiList[abi];
                         } else if (abi == PackageManager.NO_NATIVE_LIBRARIES) {
                             // Note that (non upgraded) system apps will not have any native
                             // libraries bundled in their APK, but we're guaranteed not to be
                             // such an app at this point.
-                            pkg.applicationInfo.cpuAbi = null;
+                            if (abiOverride != null || hasLegacyRenderscriptBitcode) {
+                                pkg.applicationInfo.cpuAbi = abiList[0];
+                            } else {
+                                pkg.applicationInfo.cpuAbi = null;
+                            }
                         } else {
                             mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
                             return null;
                         }
-                        handle.close();
                     }
 
                     if (DEBUG_INSTALL) Slog.i(TAG, "Linking native library dir for " + path);
@@ -5481,8 +5508,12 @@
                         }
                     }
                 }
+
+                pkgSetting.cpuAbiString = pkg.applicationInfo.cpuAbi;
             } catch (IOException ioe) {
                 Slog.e(TAG, "Unable to get canonical file " + ioe.toString());
+            } finally {
+                handle.close();
             }
         }
         pkg.mScanPath = path;
@@ -6174,8 +6205,8 @@
         }
     }
 
-    private static int copyNativeLibrariesForInternalApp(File scanFile, final File nativeLibraryDir)
-            throws IOException {
+    private static int copyNativeLibrariesForInternalApp(ApkHandle handle,
+            final File nativeLibraryDir, String[] abiList) throws IOException {
         if (!nativeLibraryDir.isDirectory()) {
             nativeLibraryDir.delete();
 
@@ -6197,21 +6228,16 @@
          * If this is an internal application or our nativeLibraryPath points to
          * the app-lib directory, unpack the libraries if necessary.
          */
-        final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(scanFile);
-        try {
-            int abi = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_ABIS);
-            if (abi >= 0) {
-                int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle,
-                        nativeLibraryDir, Build.SUPPORTED_ABIS[abi]);
-                if (copyRet != PackageManager.INSTALL_SUCCEEDED) {
-                    return copyRet;
-                }
+        int abi = NativeLibraryHelper.findSupportedAbi(handle, abiList);
+        if (abi >= 0) {
+            int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle,
+                    nativeLibraryDir, Build.SUPPORTED_ABIS[abi]);
+            if (copyRet != PackageManager.INSTALL_SUCCEEDED) {
+                return copyRet;
             }
-
-            return abi;
-        } finally {
-            handle.close();
         }
+
+        return abi;
     }
 
     private void killApplication(String pkgName, int appId, String reason) {
@@ -7535,7 +7561,7 @@
                         }
                         p = scanPackageLI(fullPath, flags,
                                 SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME,
-                                System.currentTimeMillis(), UserHandle.ALL);
+                                System.currentTimeMillis(), UserHandle.ALL, null);
                         if (p != null) {
                             /*
                              * TODO this seems dangerous as the package may have
@@ -7656,6 +7682,16 @@
         if (observer == null && observer2 == null) {
             throw new IllegalArgumentException("No install observer supplied");
         }
+        installPackageWithVerificationEncryptionAndAbiOverrideEtc(packageURI, observer, observer2,
+                flags, installerPackageName, verificationParams, encryptionParams, null);
+    }
+
+    @Override
+    public void installPackageWithVerificationEncryptionAndAbiOverrideEtc(Uri packageURI,
+            IPackageInstallObserver observer, IPackageInstallObserver2 observer2,
+            int flags, String installerPackageName,
+            VerificationParams verificationParams, ContainerEncryptionParams encryptionParams,
+            String packageAbiOverride) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,
                 null);
 
@@ -7695,7 +7731,8 @@
 
         final Message msg = mHandler.obtainMessage(INIT_COPY);
         msg.obj = new InstallParams(packageURI, observer, observer2, filteredFlags,
-                installerPackageName, verificationParams, encryptionParams, user);
+                installerPackageName, verificationParams, encryptionParams, user,
+                packageAbiOverride);
         mHandler.sendMessage(msg);
     }
 
@@ -8400,11 +8437,14 @@
         private int mRet;
         private File mTempPackage;
         final ContainerEncryptionParams encryptionParams;
+        final String packageAbiOverride;
+        final String packageInstructionSetOverride;
 
         InstallParams(Uri packageURI,
                 IPackageInstallObserver observer, IPackageInstallObserver2 observer2,
                 int flags, String installerPackageName, VerificationParams verificationParams,
-                ContainerEncryptionParams encryptionParams, UserHandle user) {
+                ContainerEncryptionParams encryptionParams, UserHandle user,
+                String packageAbiOverride) {
             super(user);
             this.mPackageURI = packageURI;
             this.flags = flags;
@@ -8413,6 +8453,9 @@
             this.installerPackageName = installerPackageName;
             this.verificationParams = verificationParams;
             this.encryptionParams = encryptionParams;
+            this.packageAbiOverride = packageAbiOverride;
+            this.packageInstructionSetOverride = (packageAbiOverride == null) ?
+                    packageAbiOverride : VMRuntime.getInstructionSet(packageAbiOverride);
         }
 
         @Override
@@ -8558,7 +8601,7 @@
                         // Remote call to find out default install location
                         final String packageFilePath = packageFile.getAbsolutePath();
                         pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags,
-                                lowThreshold);
+                                lowThreshold, packageAbiOverride);
 
                         /*
                          * If we have too little free space, try to free cache
@@ -8567,10 +8610,10 @@
                         if (pkgLite.recommendedInstallLocation
                                 == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
                             final long size = mContainerService.calculateInstalledSize(
-                                    packageFilePath, isForwardLocked());
+                                    packageFilePath, isForwardLocked(), packageAbiOverride);
                             if (mInstaller.freeCache(size + lowThreshold) >= 0) {
                                 pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath,
-                                        flags, lowThreshold);
+                                        flags, lowThreshold, packageAbiOverride);
                             }
                             /*
                              * The cache free must have deleted the file we
@@ -8990,11 +9033,12 @@
         final ManifestDigest manifestDigest;
         final UserHandle user;
         final String instructionSet;
+        final String abiOverride;
 
         InstallArgs(Uri packageURI,
                 IPackageInstallObserver observer, IPackageInstallObserver2 observer2,
                 int flags, String installerPackageName, ManifestDigest manifestDigest,
-                UserHandle user, String instructionSet) {
+                UserHandle user, String instructionSet, String abiOverride) {
             this.packageURI = packageURI;
             this.flags = flags;
             this.observer = observer;
@@ -9003,6 +9047,7 @@
             this.manifestDigest = manifestDigest;
             this.user = user;
             this.instructionSet = instructionSet;
+            this.abiOverride = abiOverride;
         }
 
         abstract void createCopyFile();
@@ -9058,12 +9103,13 @@
         FileInstallArgs(InstallParams params) {
             super(params.getPackageUri(), params.observer, params.observer2, params.flags,
                     params.installerPackageName, params.getManifestDigest(),
-                    params.getUser(), null /* instruction set */);
+                    params.getUser(), params.packageInstructionSetOverride,
+                    params.packageAbiOverride);
         }
 
         FileInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath,
                 String instructionSet) {
-            super(null, null, null, 0, null, null, null, instructionSet);
+            super(null, null, null, 0, null, null, null, instructionSet, null);
             File codeFile = new File(fullCodePath);
             installDir = codeFile.getParentFile();
             codeFileName = fullCodePath;
@@ -9072,7 +9118,7 @@
         }
 
         FileInstallArgs(Uri packageURI, String pkgName, String dataDir, String instructionSet) {
-            super(packageURI, null, null, 0, null, null, null, instructionSet);
+            super(packageURI, null, null, 0, null, null, null, instructionSet, null);
             installDir = isFwdLocked() ? mDrmAppPrivateInstallDir : mAppInstallDir;
             String apkName = getNextCodePath(null, pkgName, ".apk");
             codeFileName = new File(installDir, apkName + ".apk").getPath();
@@ -9176,14 +9222,26 @@
                 NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryFile);
                 nativeLibraryFile.delete();
             }
+
+            final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(codeFile);
+            String[] abiList = (abiOverride != null) ?
+                    new String[] { abiOverride } : Build.SUPPORTED_ABIS;
             try {
-                int copyRet = copyNativeLibrariesForInternalApp(codeFile, nativeLibraryFile);
+                if (Build.SUPPORTED_64_BIT_ABIS.length > 0 &&
+                        abiOverride == null &&
+                        NativeLibraryHelper.hasRenderscriptBitcode(handle)) {
+                    abiList = Build.SUPPORTED_32_BIT_ABIS;
+                }
+
+                int copyRet = copyNativeLibrariesForInternalApp(handle, nativeLibraryFile, abiList);
                 if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
                     return copyRet;
                 }
             } catch (IOException e) {
                 Slog.e(TAG, "Copying native libraries failed", e);
                 ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+            } finally {
+                handle.close();
             }
 
             return ret;
@@ -9398,14 +9456,15 @@
         AsecInstallArgs(InstallParams params) {
             super(params.getPackageUri(), params.observer, params.observer2, params.flags,
                     params.installerPackageName, params.getManifestDigest(),
-                    params.getUser(), null /* instruction set */);
+                    params.getUser(), params.packageInstructionSetOverride,
+                    params.packageAbiOverride);
         }
 
         AsecInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath,
                 String instructionSet, boolean isExternal, boolean isForwardLocked) {
             super(null, null, null, (isExternal ? PackageManager.INSTALL_EXTERNAL : 0)
                     | (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0),
-                    null, null, null, instructionSet);
+                    null, null, null, instructionSet, null);
             // Extract cid from fullCodePath
             int eidx = fullCodePath.lastIndexOf("/");
             String subStr1 = fullCodePath.substring(0, eidx);
@@ -9417,7 +9476,7 @@
         AsecInstallArgs(String cid, String instructionSet, boolean isForwardLocked) {
             super(null, null, null, (isAsecExternal(cid) ? PackageManager.INSTALL_EXTERNAL : 0)
                     | (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0),
-                    null, null, null, instructionSet);
+                    null, null, null, instructionSet, null);
             this.cid = cid;
             setCachePath(PackageHelper.getSdDir(cid));
         }
@@ -9426,7 +9485,7 @@
                 boolean isExternal, boolean isForwardLocked) {
             super(packageURI, null, null, (isExternal ? PackageManager.INSTALL_EXTERNAL : 0)
                     | (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0),
-                    null, null, null, instructionSet);
+                    null, null, null, instructionSet, null);
             this.cid = cid;
         }
 
@@ -9438,7 +9497,7 @@
             try {
                 mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
                         Intent.FLAG_GRANT_READ_URI_PERMISSION);
-                return imcs.checkExternalFreeStorage(packageURI, isFwdLocked());
+                return imcs.checkExternalFreeStorage(packageURI, isFwdLocked(), abiOverride);
             } finally {
                 mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
             }
@@ -9464,7 +9523,8 @@
                 mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
                         Intent.FLAG_GRANT_READ_URI_PERMISSION);
                 newCachePath = imcs.copyResourceToContainer(packageURI, cid, getEncryptKey(),
-                        RES_FILE_NAME, PUBLIC_RES_FILE_NAME, isExternal(), isFwdLocked());
+                        RES_FILE_NAME, PUBLIC_RES_FILE_NAME, isExternal(), isFwdLocked(),
+                        abiOverride);
             } finally {
                 mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
             }
@@ -9772,7 +9832,7 @@
      */
     private void installNewPackageLI(PackageParser.Package pkg,
             int parseFlags, int scanMode, UserHandle user,
-            String installerPackageName, PackageInstalledInfo res) {
+            String installerPackageName, PackageInstalledInfo res, String abiOverride) {
         // Remember this for later, in case we need to rollback this install
         String pkgName = pkg.packageName;
 
@@ -9800,7 +9860,7 @@
         }
         mLastScanError = PackageManager.INSTALL_SUCCEEDED;
         PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode,
-                System.currentTimeMillis(), user);
+                System.currentTimeMillis(), user, abiOverride);
         if (newPackage == null) {
             Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath);
             if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
@@ -9827,7 +9887,7 @@
 
     private void replacePackageLI(PackageParser.Package pkg,
             int parseFlags, int scanMode, UserHandle user,
-            String installerPackageName, PackageInstalledInfo res) {
+            String installerPackageName, PackageInstalledInfo res, String abiOverride) {
 
         PackageParser.Package oldPackage;
         String pkgName = pkg.packageName;
@@ -9856,17 +9916,19 @@
         boolean sysPkg = (isSystemApp(oldPackage));
         if (sysPkg) {
             replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanMode,
-                    user, allUsers, perUserInstalled, installerPackageName, res);
+                    user, allUsers, perUserInstalled, installerPackageName, res,
+                    abiOverride);
         } else {
             replaceNonSystemPackageLI(oldPackage, pkg, parseFlags, scanMode,
-                    user, allUsers, perUserInstalled, installerPackageName, res);
+                    user, allUsers, perUserInstalled, installerPackageName, res,
+                    abiOverride);
         }
     }
 
     private void replaceNonSystemPackageLI(PackageParser.Package deletedPackage,
             PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user,
             int[] allUsers, boolean[] perUserInstalled,
-            String installerPackageName, PackageInstalledInfo res) {
+            String installerPackageName, PackageInstalledInfo res, String abiOverride) {
         PackageParser.Package newPackage = null;
         String pkgName = deletedPackage.packageName;
         boolean deletedPkg = true;
@@ -9891,7 +9953,7 @@
             // Successfully deleted the old package. Now proceed with re-installation
             mLastScanError = PackageManager.INSTALL_SUCCEEDED;
             newPackage = scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_TIME,
-                    System.currentTimeMillis(), user);
+                    System.currentTimeMillis(), user, abiOverride);
             if (newPackage == null) {
                 Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath);
                 if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
@@ -9920,7 +9982,7 @@
             }
             // Since we failed to install the new package we need to restore the old
             // package that we deleted.
-            if(deletedPkg) {
+            if (deletedPkg) {
                 if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, reinstalling: " + deletedPackage);
                 File restoreFile = new File(deletedPackage.mPath);
                 // Parse old package
@@ -9931,7 +9993,7 @@
                 int oldScanMode = (oldOnSd ? 0 : SCAN_MONITOR) | SCAN_UPDATE_SIGNATURE
                         | SCAN_UPDATE_TIME;
                 if (scanPackageLI(restoreFile, oldParseFlags, oldScanMode,
-                        origUpdateTime, null) == null) {
+                        origUpdateTime, null, null) == null) {
                     Slog.e(TAG, "Failed to restore package : " + pkgName + " after failed upgrade");
                     return;
                 }
@@ -9951,7 +10013,7 @@
     private void replaceSystemPackageLI(PackageParser.Package deletedPackage,
             PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user,
             int[] allUsers, boolean[] perUserInstalled,
-            String installerPackageName, PackageInstalledInfo res) {
+            String installerPackageName, PackageInstalledInfo res, String abiOverride) {
         if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg
                 + ", old=" + deletedPackage);
         PackageParser.Package newPackage = null;
@@ -10005,7 +10067,7 @@
         // Successfully disabled the old package. Now proceed with re-installation
         res.returnCode = mLastScanError = PackageManager.INSTALL_SUCCEEDED;
         pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
-        newPackage = scanPackageLI(pkg, parseFlags, scanMode, 0, user);
+        newPackage = scanPackageLI(pkg, parseFlags, scanMode, 0, user, abiOverride);
         if (newPackage == null) {
             Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath);
             if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
@@ -10039,7 +10101,7 @@
                 removeInstalledPackageLI(newPackage, true);
             }
             // Add back the old system package
-            scanPackageLI(oldPkg, parseFlags, SCAN_MONITOR | SCAN_UPDATE_SIGNATURE, 0, user);
+            scanPackageLI(oldPkg, parseFlags, SCAN_MONITOR | SCAN_UPDATE_SIGNATURE, 0, user, null);
             // Restore the old system information in Settings
             synchronized(mPackages) {
                 if (updatedSettings) {
@@ -10283,10 +10345,10 @@
         pkg.applicationInfo.nativeLibraryDir = args.getNativeLibraryPath();
         if (replace) {
             replacePackageLI(pkg, parseFlags, scanMode, args.user,
-                    installerPackageName, res);
+                    installerPackageName, res, args.abiOverride);
         } else {
             installNewPackageLI(pkg, parseFlags, scanMode | SCAN_DELETE_DATA_ON_FAILURES, args.user,
-                    installerPackageName, res);
+                    installerPackageName, res, args.abiOverride);
         }
         synchronized (mPackages) {
             final PackageSetting ps = mSettings.mPackages.get(pkgName);
@@ -10703,7 +10765,7 @@
             parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
         }
         PackageParser.Package newPkg = scanPackageLI(disabledPs.codePath,
-                parseFlags, SCAN_MONITOR | SCAN_NO_PATHS, 0, null);
+                parseFlags, SCAN_MONITOR | SCAN_NO_PATHS, 0, null, null);
 
         if (newPkg == null) {
             Slog.w(TAG, "Failed to restore system package:" + newPs.name
@@ -11482,33 +11544,34 @@
     }
 
     @Override
-    public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int userIdOrig,
-            int userIdDest) {
+    public void addCrossProfileIntentFilter(IntentFilter filter, boolean removable,
+            int sourceUserId, int targetUserId) {
         mContext.enforceCallingOrSelfPermission(
                         android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         if (filter.countActions() == 0) {
-            Slog.w(TAG, "Cannot set a forwarding intent filter with no filter actions");
+            Slog.w(TAG, "Cannot set a crossProfile intent filter with no filter actions");
             return;
         }
         synchronized (mPackages) {
-            mSettings.editForwardingIntentResolverLPw(userIdOrig).addFilter(
-                    new ForwardingIntentFilter(filter, removable, userIdDest));
-            mSettings.writePackageRestrictionsLPr(userIdOrig);
+            mSettings.editCrossProfileIntentResolverLPw(sourceUserId).addFilter(
+                    new CrossProfileIntentFilter(filter, removable, targetUserId));
+            mSettings.writePackageRestrictionsLPr(sourceUserId);
         }
     }
 
     @Override
-    public void clearForwardingIntentFilters(int userIdOrig) {
+    public void clearCrossProfileIntentFilters(int sourceUserId) {
         mContext.enforceCallingOrSelfPermission(
                         android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         synchronized (mPackages) {
-            ForwardingIntentResolver fir = mSettings.editForwardingIntentResolverLPw(userIdOrig);
-            HashSet<ForwardingIntentFilter> set =
-                    new HashSet<ForwardingIntentFilter>(fir.filterSet());
-            for (ForwardingIntentFilter fif : set) {
-                if (fif.isRemovable()) fir.removeFilter(fif);
+            CrossProfileIntentResolver cpir =
+                    mSettings.editCrossProfileIntentResolverLPw(sourceUserId);
+            HashSet<CrossProfileIntentFilter> set =
+                    new HashSet<CrossProfileIntentFilter>(cpir.filterSet());
+            for (CrossProfileIntentFilter cpif : set) {
+                if (cpif.isRemovable()) cpir.removeFilter(cpif);
             }
-            mSettings.writePackageRestrictionsLPr(userIdOrig);
+            mSettings.writePackageRestrictionsLPr(sourceUserId);
         }
     }
 
@@ -12515,7 +12578,7 @@
                 doGc = true;
                 synchronized (mInstallLock) {
                     final PackageParser.Package pkg = scanPackageLI(new File(codePath), parseFlags,
-                            0, 0, null);
+                            0, 0, null, null);
                     // Scan the package
                     if (pkg != null) {
                         /*
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index d70c725..c78249b 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -52,25 +52,49 @@
     private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false;
 
     // Signature seinfo values read from policy.
-    private static HashMap<Signature, Policy> sSigSeinfo =
-        new HashMap<Signature, Policy>();
+    private static HashMap<Signature, Policy> sSigSeinfo = new HashMap<Signature, Policy>();
 
     // Default seinfo read from policy.
     private static String sDefaultSeinfo = null;
 
-    // Locations of potential install policy files.
-    private static final File[] INSTALL_POLICY_FILE = {
-        new File(Environment.getDataDirectory(), "security/mac_permissions.xml"),
-        new File(Environment.getRootDirectory(), "etc/security/mac_permissions.xml"),
-        null};
+    // Data policy override version file.
+    private static final String DATA_VERSION_FILE =
+            Environment.getDataDirectory() + "/security/current/selinux_version";
 
-    // Location of seapp_contexts policy file.
-    private static final String SEAPP_CONTEXTS_FILE = "/seapp_contexts";
+    // Base policy version file.
+    private static final String BASE_VERSION_FILE = "/selinux_version";
+
+    // Whether override security policies should be loaded.
+    private static final boolean USE_OVERRIDE_POLICY = useOverridePolicy();
+
+    // Data override mac_permissions.xml policy file.
+    private static final String DATA_MAC_PERMISSIONS =
+            Environment.getDataDirectory() + "/security/current/mac_permissions.xml";
+
+    // Base mac_permissions.xml policy file.
+    private static final String BASE_MAC_PERMISSIONS =
+            Environment.getRootDirectory() + "/etc/security/mac_permissions.xml";
+
+    // Determine which mac_permissions.xml file to use.
+    private static final String MAC_PERMISSIONS = USE_OVERRIDE_POLICY ?
+            DATA_MAC_PERMISSIONS : BASE_MAC_PERMISSIONS;
+
+    // Data override seapp_contexts policy file.
+    private static final String DATA_SEAPP_CONTEXTS =
+            Environment.getDataDirectory() + "/security/current/seapp_contexts";
+
+    // Base seapp_contexts policy file.
+    private static final String BASE_SEAPP_CONTEXTS = "/seapp_contexts";
+
+    // Determine which seapp_contexts file to use.
+    private static final String SEAPP_CONTEXTS = USE_OVERRIDE_POLICY ?
+            DATA_SEAPP_CONTEXTS : BASE_SEAPP_CONTEXTS;
 
     // Stores the hash of the last used seapp_contexts file.
     private static final String SEAPP_HASH_FILE =
             Environment.getDataDirectory().toString() + "/system/seapp_hash";
 
+
     // Signature policy stanzas
     static class Policy {
         private String seinfo;
@@ -112,51 +136,17 @@
         sDefaultSeinfo = null;
     }
 
-    /**
-     * Parses an MMAC install policy from a predefined list of locations.
-     * @return boolean indicating whether an install policy was correctly parsed.
-     */
     public static boolean readInstallPolicy() {
-
-        return readInstallPolicy(INSTALL_POLICY_FILE);
-    }
-
-    /**
-     * Parses an MMAC install policy given as an argument.
-     * @param policyFile object representing the path of the policy.
-     * @return boolean indicating whether the install policy was correctly parsed.
-     */
-    public static boolean readInstallPolicy(File policyFile) {
-
-        return readInstallPolicy(new File[]{policyFile,null});
-    }
-
-    private static boolean readInstallPolicy(File[] policyFiles) {
         // Temp structures to hold the rules while we parse the xml file.
         // We add all the rules together once we know there's no structural problems.
         HashMap<Signature, Policy> sigSeinfo = new HashMap<Signature, Policy>();
         String defaultSeinfo = null;
 
         FileReader policyFile = null;
-        int i = 0;
-        while (policyFile == null && policyFiles != null && policyFiles[i] != null) {
-            try {
-                policyFile = new FileReader(policyFiles[i]);
-                break;
-            } catch (FileNotFoundException e) {
-                Slog.d(TAG,"Couldn't find install policy " + policyFiles[i].getPath());
-            }
-            i++;
-        }
-
-        if (policyFile == null) {
-            Slog.d(TAG, "No policy file found. All seinfo values will be null.");
-            return false;
-        }
-
-        Slog.d(TAG, "Using install policy file " + policyFiles[i].getPath());
-
         try {
+            policyFile = new FileReader(MAC_PERMISSIONS);
+            Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS);
+
             XmlPullParser parser = Xml.newPullParser();
             parser.setInput(policyFile);
 
@@ -199,20 +189,14 @@
                     XmlUtils.skipCurrentTag(parser);
                 }
             }
-        } catch (XmlPullParserException e) {
-            // An error outside of a stanza means a structural problem
-            // with the xml file. So ignore it.
-            Slog.w(TAG, "Got exception parsing ", e);
+        } catch (XmlPullParserException xpe) {
+            Slog.w(TAG, "Got exception parsing " + MAC_PERMISSIONS, xpe);
             return false;
-        } catch (IOException e) {
-            Slog.w(TAG, "Got exception parsing ", e);
+        } catch (IOException ioe) {
+            Slog.w(TAG, "Got exception parsing " + MAC_PERMISSIONS, ioe);
             return false;
         } finally {
-            try {
-                policyFile.close();
-            } catch (IOException e) {
-                //omit
-            }
+            IoUtils.closeQuietly(policyFile);
         }
 
         flushInstallPolicy();
@@ -412,7 +396,7 @@
         // Any error with the seapp_contexts file should be fatal
         byte[] currentHash = null;
         try {
-            currentHash = returnHash(SEAPP_CONTEXTS_FILE);
+            currentHash = returnHash(SEAPP_CONTEXTS);
         } catch (IOException ioe) {
             Slog.e(TAG, "Error with hashing seapp_contexts.", ioe);
             return false;
@@ -434,7 +418,7 @@
      */
     public static void setRestoreconDone() {
         try {
-            final byte[] currentHash = returnHash(SEAPP_CONTEXTS_FILE);
+            final byte[] currentHash = returnHash(SEAPP_CONTEXTS);
             dumpHash(new File(SEAPP_HASH_FILE), currentHash);
         } catch (IOException ioe) {
             Slog.e(TAG, "Error with saving hash to " + SEAPP_HASH_FILE, ioe);
@@ -485,4 +469,21 @@
             throw new RuntimeException(nsae);  // impossible
         }
     }
+
+    private static boolean useOverridePolicy() {
+        try {
+            final String overrideVersion = IoUtils.readFileAsString(DATA_VERSION_FILE);
+            final String baseVersion = IoUtils.readFileAsString(BASE_VERSION_FILE);
+            if (overrideVersion.equals(baseVersion)) {
+                return true;
+            }
+            Slog.e(TAG, "Override policy version '" + overrideVersion + "' doesn't match " +
+                   "base version '" + baseVersion + "'. Skipping override policy files.");
+        } catch (FileNotFoundException fnfe) {
+            // Override version file doesn't have to exist so silently ignore.
+        } catch (IOException ioe) {
+            Slog.w(TAG, "Skipping override policy files.", ioe);
+        }
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 3ca658f..3483fae 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -132,6 +132,9 @@
     private static final String TAG_PACKAGE = "pkg";
     private static final String TAG_PERSISTENT_PREFERRED_ACTIVITIES =
             "persistent-preferred-activities";
+    static final String TAG_CROSS_PROFILE_INTENT_FILTERS =
+            "crossProfile-intent-filters";
+    //Old name. Kept for compatibility
     static final String TAG_FORWARDING_INTENT_FILTERS =
             "forwarding-intent-filters";
 
@@ -189,8 +192,8 @@
             new SparseArray<PersistentPreferredIntentResolver>();
 
     // For every user, it is used to find to which other users the intent can be forwarded.
-    final SparseArray<ForwardingIntentResolver> mForwardingIntentResolvers =
-            new SparseArray<ForwardingIntentResolver>();
+    final SparseArray<CrossProfileIntentResolver> mCrossProfileIntentResolvers =
+            new SparseArray<CrossProfileIntentResolver>();
 
     final HashMap<String, SharedUserSetting> mSharedUsers =
             new HashMap<String, SharedUserSetting>();
@@ -856,13 +859,13 @@
         return ppir;
     }
 
-    ForwardingIntentResolver editForwardingIntentResolverLPw(int userId) {
-        ForwardingIntentResolver fir = mForwardingIntentResolvers.get(userId);
-        if (fir == null) {
-            fir = new ForwardingIntentResolver();
-            mForwardingIntentResolvers.put(userId, fir);
+    CrossProfileIntentResolver editCrossProfileIntentResolverLPw(int userId) {
+        CrossProfileIntentResolver cpir = mCrossProfileIntentResolvers.get(userId);
+        if (cpir == null) {
+            cpir = new CrossProfileIntentResolver();
+            mCrossProfileIntentResolvers.put(userId, cpir);
         }
-        return fir;
+        return cpir;
     }
 
     private File getUserPackagesStateFile(int userId) {
@@ -980,7 +983,7 @@
         }
     }
 
-    private void readForwardingIntentFiltersLPw(XmlPullParser parser, int userId)
+    private void readCrossProfileIntentFiltersLPw(XmlPullParser parser, int userId)
             throws XmlPullParserException, IOException {
         int outerDepth = parser.getDepth();
         int type;
@@ -991,10 +994,10 @@
             }
             String tagName = parser.getName();
             if (tagName.equals(TAG_ITEM)) {
-                ForwardingIntentFilter fif = new ForwardingIntentFilter(parser);
-                editForwardingIntentResolverLPw(userId).addFilter(fif);
+                CrossProfileIntentFilter cpif = new CrossProfileIntentFilter(parser);
+                editCrossProfileIntentResolverLPw(userId).addFilter(cpif);
             } else {
-                String msg = "Unknown element under " +  TAG_FORWARDING_INTENT_FILTERS + ": " +
+                String msg = "Unknown element under " +  TAG_CROSS_PROFILE_INTENT_FILTERS + ": " +
                         parser.getName();
                 PackageManagerService.reportSettingsProblem(Log.WARN, msg);
                 XmlUtils.skipCurrentTag(parser);
@@ -1130,8 +1133,9 @@
                     readPreferredActivitiesLPw(parser, userId);
                 } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
                     readPersistentPreferredActivitiesLPw(parser, userId);
-                } else if (tagName.equals(TAG_FORWARDING_INTENT_FILTERS)) {
-                    readForwardingIntentFiltersLPw(parser, userId);
+                } else if (tagName.equals(TAG_FORWARDING_INTENT_FILTERS)
+                        || tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) {
+                    readCrossProfileIntentFiltersLPw(parser, userId);
                 } else {
                     Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: "
                           + parser.getName());
@@ -1209,18 +1213,18 @@
         serializer.endTag(null, TAG_PERSISTENT_PREFERRED_ACTIVITIES);
     }
 
-    void writeForwardingIntentFiltersLPr(XmlSerializer serializer, int userId)
+    void writeCrossProfileIntentFiltersLPr(XmlSerializer serializer, int userId)
             throws IllegalArgumentException, IllegalStateException, IOException {
-        serializer.startTag(null, TAG_FORWARDING_INTENT_FILTERS);
-        ForwardingIntentResolver fir = mForwardingIntentResolvers.get(userId);
-        if (fir != null) {
-            for (final ForwardingIntentFilter fif : fir.filterSet()) {
+        serializer.startTag(null, TAG_CROSS_PROFILE_INTENT_FILTERS);
+        CrossProfileIntentResolver cpir = mCrossProfileIntentResolvers.get(userId);
+        if (cpir != null) {
+            for (final CrossProfileIntentFilter cpif : cpir.filterSet()) {
                 serializer.startTag(null, TAG_ITEM);
-                fif.writeToXml(serializer);
+                cpif.writeToXml(serializer);
                 serializer.endTag(null, TAG_ITEM);
             }
         }
-        serializer.endTag(null, TAG_FORWARDING_INTENT_FILTERS);
+        serializer.endTag(null, TAG_CROSS_PROFILE_INTENT_FILTERS);
     }
 
     void writePackageRestrictionsLPr(int userId) {
@@ -1321,7 +1325,7 @@
 
             writePersistentPreferredActivitiesLPr(serializer, userId);
 
-            writeForwardingIntentFiltersLPr(serializer, userId);
+            writeCrossProfileIntentFiltersLPr(serializer, userId);
 
             serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS);
 
@@ -1940,10 +1944,11 @@
                     // TODO: check whether this is okay! as it is very
                     // similar to how preferred-activities are treated
                     readPersistentPreferredActivitiesLPw(parser, 0);
-                } else if (tagName.equals(TAG_FORWARDING_INTENT_FILTERS)) {
+                } else if (tagName.equals(TAG_FORWARDING_INTENT_FILTERS)
+                        || tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) {
                     // TODO: check whether this is okay! as it is very
                     // similar to how preferred-activities are treated
-                    readForwardingIntentFiltersLPw(parser, 0);
+                    readCrossProfileIntentFiltersLPw(parser, 0);
                 } else if (tagName.equals("updated-package")) {
                     readDisabledSysPackageLPw(parser);
                 } else if (tagName.equals("cleaning-package")) {
@@ -2935,6 +2940,26 @@
         file.delete();
         file = getUserPackagesStateBackupFile(userId);
         file.delete();
+        removeCrossProfileIntentFiltersToUserLPr(userId);
+    }
+
+    void removeCrossProfileIntentFiltersToUserLPr(int targetUserId) {
+        for (int i = 0; i < mCrossProfileIntentResolvers.size(); i++) {
+            int sourceUserId = mCrossProfileIntentResolvers.keyAt(i);
+            CrossProfileIntentResolver cpir = mCrossProfileIntentResolvers.get(sourceUserId);
+            boolean needsWriting = false;
+            HashSet<CrossProfileIntentFilter> cpifs =
+                    new HashSet<CrossProfileIntentFilter>(cpir.filterSet());
+            for (CrossProfileIntentFilter cpif : cpifs) {
+                if (cpif.getTargetUserId() == targetUserId) {
+                    needsWriting = true;
+                    cpir.removeFilter(cpif);
+                }
+            }
+            if (needsWriting) {
+                writePackageRestrictionsLPr(sourceUserId);
+            }
+        }
     }
 
     // This should be called (at least) whenever an application is removed
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a8f6116..0cb2ab9 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -107,6 +107,7 @@
     private static final String ATTR_TYPE_STRING_ARRAY = "sa";
     private static final String ATTR_TYPE_STRING = "s";
     private static final String ATTR_TYPE_BOOLEAN = "b";
+    private static final String ATTR_TYPE_INTEGER = "i";
 
     private static final String USER_INFO_DIR = "system" + File.separator + "users";
     private static final String USER_LIST_FILENAME = "userlist.xml";
@@ -160,7 +161,6 @@
     private final SparseBooleanArray mRemovingUserIds = new SparseBooleanArray();
 
     private int[] mUserIds;
-    private boolean mGuestEnabled;
     private int mNextSerialNumber;
     private int mUserVersion = 0;
 
@@ -426,43 +426,6 @@
         }
     }
 
-    @Override
-    public void setGuestEnabled(boolean enable) {
-        checkManageUsersPermission("enable guest users");
-        synchronized (mPackagesLock) {
-            if (mGuestEnabled != enable) {
-                mGuestEnabled = enable;
-                // Erase any guest user that currently exists
-                for (int i = 0; i < mUsers.size(); i++) {
-                    UserInfo user = mUsers.valueAt(i);
-                    if (!user.partial && user.isGuest()) {
-                        if (!enable) {
-                            removeUser(user.id);
-                        }
-                        return;
-                    }
-                }
-                // No guest was found
-                if (enable) {
-                    createUser("Guest", UserInfo.FLAG_GUEST);
-                }
-            }
-        }
-    }
-
-    @Override
-    public boolean isGuestEnabled() {
-        synchronized (mPackagesLock) {
-            return mGuestEnabled;
-        }
-    }
-
-    @Override
-    public void wipeUser(int userHandle) {
-        checkManageUsersPermission("wipe user");
-        // TODO:
-    }
-
     public void makeInitialized(int userId) {
         checkManageUsersPermission("makeInitialized");
         synchronized (mPackagesLock) {
@@ -582,7 +545,6 @@
     }
 
     private void readUserListLocked() {
-        mGuestEnabled = false;
         if (!mUserListFile.exists()) {
             fallbackToSingleUserLocked();
             return;
@@ -624,9 +586,6 @@
 
                     if (user != null) {
                         mUsers.put(user.id, user);
-                        if (user.isGuest()) {
-                            mGuestEnabled = true;
-                        }
                         if (mNextSerialNumber < 0 || mNextSerialNumber <= user.id) {
                             mNextSerialNumber = user.id + 1;
                         }
@@ -1140,53 +1099,57 @@
      */
     public boolean removeUser(int userHandle) {
         checkManageUsersPermission("Only the system can remove users");
-        final UserInfo user;
-        synchronized (mPackagesLock) {
-            user = mUsers.get(userHandle);
-            if (userHandle == 0 || user == null) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            final UserInfo user;
+            synchronized (mPackagesLock) {
+                user = mUsers.get(userHandle);
+                if (userHandle == 0 || user == null) {
+                    return false;
+                }
+                mRemovingUserIds.put(userHandle, true);
+                try {
+                    mAppOpsService.removeUser(userHandle);
+                } catch (RemoteException e) {
+                    Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user", e);
+                }
+                // Set this to a partially created user, so that the user will be purged
+                // on next startup, in case the runtime stops now before stopping and
+                // removing the user completely.
+                user.partial = true;
+                // Mark it as disabled, so that it isn't returned any more when
+                // profiles are queried.
+                user.flags |= UserInfo.FLAG_DISABLED;
+                writeUserLocked(user);
+            }
+
+            if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
+                    && user.isManagedProfile()) {
+                // Send broadcast to notify system that the user removed was a
+                // managed user.
+                sendProfileRemovedBroadcast(user.profileGroupId, user.id);
+            }
+
+            if (DBG) Slog.i(LOG_TAG, "Stopping user " + userHandle);
+            int res;
+            try {
+                res = ActivityManagerNative.getDefault().stopUser(userHandle,
+                        new IStopUserCallback.Stub() {
+                            @Override
+                            public void userStopped(int userId) {
+                                finishRemoveUser(userId);
+                            }
+                            @Override
+                            public void userStopAborted(int userId) {
+                            }
+                        });
+            } catch (RemoteException e) {
                 return false;
             }
-            mRemovingUserIds.put(userHandle, true);
-            try {
-                mAppOpsService.removeUser(userHandle);
-            } catch (RemoteException e) {
-                Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user", e);
-            }
-            // Set this to a partially created user, so that the user will be purged
-            // on next startup, in case the runtime stops now before stopping and
-            // removing the user completely.
-            user.partial = true;
-            // Mark it as disabled, so that it isn't returned any more when
-            // profiles are queried.
-            user.flags |= UserInfo.FLAG_DISABLED;
-            writeUserLocked(user);
+            return res == ActivityManager.USER_OP_SUCCESS;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
-
-        if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
-                && user.isManagedProfile()) {
-            // Send broadcast to notify system that the user removed was a
-            // managed user.
-            sendProfileRemovedBroadcast(user.profileGroupId, user.id);
-        }
-
-        if (DBG) Slog.i(LOG_TAG, "Stopping user " + userHandle);
-        int res;
-        try {
-            res = ActivityManagerNative.getDefault().stopUser(userHandle,
-                    new IStopUserCallback.Stub() {
-                        @Override
-                        public void userStopped(int userId) {
-                            finishRemoveUser(userId);
-                        }
-                        @Override
-                        public void userStopAborted(int userId) {
-                        }
-            });
-        } catch (RemoteException e) {
-            return false;
-        }
-
-        return res == ActivityManager.USER_OP_SUCCESS;
     }
 
     void finishRemoveUser(final int userHandle) {
@@ -1529,16 +1492,18 @@
                         String [] valueStrings = new String[values.size()];
                         values.toArray(valueStrings);
                         restrictions.putStringArray(key, valueStrings);
-                    } else if (ATTR_TYPE_BOOLEAN.equals(valType)) {
-                        restrictions.putBoolean(key, Boolean.parseBoolean(
-                                parser.nextText().trim()));
                     } else {
                         String value = parser.nextText().trim();
-                        restrictions.putString(key, value);
+                        if (ATTR_TYPE_BOOLEAN.equals(valType)) {
+                            restrictions.putBoolean(key, Boolean.parseBoolean(value));
+                        } else if (ATTR_TYPE_INTEGER.equals(valType)) {
+                            restrictions.putInt(key, Integer.parseInt(value));
+                        } else {
+                            restrictions.putString(key, value);
+                        }
                     }
                 }
             }
-
         } catch (IOException ioe) {
         } catch (XmlPullParserException pe) {
         } finally {
@@ -1578,6 +1543,9 @@
                 if (value instanceof Boolean) {
                     serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BOOLEAN);
                     serializer.text(value.toString());
+                } else if (value instanceof Integer) {
+                    serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_INTEGER);
+                    serializer.text(value.toString());
                 } else if (value == null || value instanceof String) {
                     serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING);
                     serializer.text(value != null ? (String) value : "");
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 462b234..b9ef492 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -40,6 +40,7 @@
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
+import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
@@ -185,12 +186,16 @@
                 if (resolveInfo.serviceInfo == null) continue;
 
                 String packageName = resolveInfo.serviceInfo.packageName;
+                // STOPSHIP Reenable this check once the GMS Core prebuild library has the
+                // permission.
+                /*
                 if (pm.checkPermission(PERMISSION_PROVIDE_AGENT, packageName)
                         != PackageManager.PERMISSION_GRANTED) {
                     Log.w(TAG, "Skipping agent because package " + packageName
                             + " does not have permission " + PERMISSION_PROVIDE_AGENT + ".");
                     continue;
                 }
+                */
 
                 ComponentName name = getComponentName(resolveInfo);
                 if (!enabledAgents.contains(name)) continue;
@@ -337,8 +342,12 @@
         for (int i = 0; i < mTrustListeners.size(); i++) {
             try {
                 mTrustListeners.get(i).onTrustChanged(enabled, userId);
+            } catch (DeadObjectException e) {
+                if (DEBUG) Slog.d(TAG, "Removing dead TrustListener.");
+                mTrustListeners.remove(i);
+                i--;
             } catch (RemoteException e) {
-                Slog.e(TAG, "Exception while notifying TrustListener. Removing listener.", e);
+                Slog.e(TAG, "Exception while notifying TrustListener.", e);
             }
         }
     }
diff --git a/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java b/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
index 3c960c7..24318df 100644
--- a/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
@@ -40,12 +40,21 @@
     private static final String fileContextsPath = "file_contexts";
     private static final String propertyContextsPath = "property_contexts";
     private static final String seappContextsPath = "seapp_contexts";
+    private static final String versionPath = "selinux_version";
+    private static final String macPermissionsPath = "mac_permissions.xml";
+    private static final String serviceContextsPath = "service_contexts";
 
     public SELinuxPolicyInstallReceiver() {
         super("/data/security/bundle", "sepolicy_bundle", "metadata/", "version");
     }
 
     private void backupContexts(File contexts) {
+        new File(contexts, versionPath).renameTo(
+                new File(contexts, versionPath + "_backup"));
+
+        new File(contexts, macPermissionsPath).renameTo(
+                new File(contexts, macPermissionsPath + "_backup"));
+
         new File(contexts, seappContextsPath).renameTo(
                 new File(contexts, seappContextsPath + "_backup"));
 
@@ -57,13 +66,19 @@
 
         new File(contexts, sepolicyPath).renameTo(
                 new File(contexts, sepolicyPath + "_backup"));
+
+        new File(contexts, serviceContextsPath).renameTo(
+                new File(contexts, serviceContextsPath + "_backup"));
     }
 
     private void copyUpdate(File contexts) {
+        new File(updateDir, versionPath).renameTo(new File(contexts, versionPath));
+        new File(updateDir, macPermissionsPath).renameTo(new File(contexts, macPermissionsPath));
         new File(updateDir, seappContextsPath).renameTo(new File(contexts, seappContextsPath));
         new File(updateDir, propertyContextsPath).renameTo(new File(contexts, propertyContextsPath));
         new File(updateDir, fileContextsPath).renameTo(new File(contexts, fileContextsPath));
         new File(updateDir, sepolicyPath).renameTo(new File(contexts, sepolicyPath));
+        new File(updateDir, serviceContextsPath).renameTo(new File(contexts, serviceContextsPath));
     }
 
     private int readInt(BufferedInputStream reader) throws IOException {
@@ -75,11 +90,14 @@
     }
 
     private int[] readChunkLengths(BufferedInputStream bundle) throws IOException {
-        int[] chunks = new int[4];
+        int[] chunks = new int[7];
         chunks[0] = readInt(bundle);
         chunks[1] = readInt(bundle);
         chunks[2] = readInt(bundle);
         chunks[3] = readInt(bundle);
+        chunks[4] = readInt(bundle);
+        chunks[5] = readInt(bundle);
+        chunks[6] = readInt(bundle);
         return chunks;
     }
 
@@ -94,10 +112,13 @@
         BufferedInputStream stream = new BufferedInputStream(new FileInputStream(updateContent));
         try {
             int[] chunkLengths = readChunkLengths(stream);
-            installFile(new File(updateDir, seappContextsPath), stream, chunkLengths[0]);
-            installFile(new File(updateDir, propertyContextsPath), stream, chunkLengths[1]);
-            installFile(new File(updateDir, fileContextsPath), stream, chunkLengths[2]);
-            installFile(new File(updateDir, sepolicyPath), stream, chunkLengths[3]);
+            installFile(new File(updateDir, versionPath), stream, chunkLengths[0]);
+            installFile(new File(updateDir, macPermissionsPath), stream, chunkLengths[1]);
+            installFile(new File(updateDir, seappContextsPath), stream, chunkLengths[2]);
+            installFile(new File(updateDir, propertyContextsPath), stream, chunkLengths[3]);
+            installFile(new File(updateDir, fileContextsPath), stream, chunkLengths[4]);
+            installFile(new File(updateDir, sepolicyPath), stream, chunkLengths[5]);
+            installFile(new File(updateDir, serviceContextsPath), stream, chunkLengths[6]);
         } finally {
             IoUtils.closeQuietly(stream);
         }
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index cfd09e5..6cb6b76 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -44,6 +44,7 @@
 import android.view.Surface;
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
+import android.view.ViewConfiguration;
 import android.view.WindowInfo;
 import android.view.WindowManager;
 import android.view.WindowManagerInternal.MagnificationCallbacks;
@@ -113,13 +114,13 @@
             mDisplayMagnifier.setMagnificationSpecLocked(spec);
         }
         if (mWindowsForAccessibilityObserver != null) {
-            mWindowsForAccessibilityObserver.computeChangedWindows();
+            mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
         }
     }
 
-    public void onRectangleOnScreenRequestedLocked(Rect rectangle, boolean immediate) {
+    public void onRectangleOnScreenRequestedLocked(Rect rectangle) {
         if (mDisplayMagnifier != null) {
-            mDisplayMagnifier.onRectangleOnScreenRequestedLocked(rectangle, immediate);
+            mDisplayMagnifier.onRectangleOnScreenRequestedLocked(rectangle);
         }
         // Not relevant for the window observer.
     }
@@ -129,7 +130,7 @@
             mDisplayMagnifier.onWindowLayersChangedLocked();
         }
         if (mWindowsForAccessibilityObserver != null) {
-            mWindowsForAccessibilityObserver.computeChangedWindows();
+            mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
         }
     }
 
@@ -138,7 +139,7 @@
             mDisplayMagnifier.onRotationChangedLocked(displayContent, rotation);
         }
         if (mWindowsForAccessibilityObserver != null) {
-            mWindowsForAccessibilityObserver.computeChangedWindows();
+            mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
         }
     }
 
@@ -154,7 +155,7 @@
             mDisplayMagnifier.onWindowTransitionLocked(windowState, transition);
         }
         if (mWindowsForAccessibilityObserver != null) {
-            mWindowsForAccessibilityObserver.computeChangedWindows();
+            mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
         }
     }
 
@@ -162,7 +163,16 @@
         // Not relevant for the display magnifier.
 
         if (mWindowsForAccessibilityObserver != null) {
-            mWindowsForAccessibilityObserver.computeChangedWindows();
+            mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
+        }
+    }
+
+
+    public void onSomeWindowResizedOrMovedLocked() {
+        // Not relevant for the display magnifier.
+
+        if (mWindowsForAccessibilityObserver != null) {
+            mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
         }
     }
 
@@ -247,7 +257,7 @@
             mWindowManagerService.scheduleAnimationLocked();
         }
 
-        public void onRectangleOnScreenRequestedLocked(Rect rectangle, boolean immediate) {
+        public void onRectangleOnScreenRequestedLocked(Rect rectangle) {
             if (DEBUG_RECTANGLE_REQUESTED) {
                 Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle);
             }
@@ -880,21 +890,45 @@
 
         private final WindowsForAccessibilityCallback mCallback;
 
+        private final long mRecurringAccessibilityEventsIntervalMillis;
+
         public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
                 WindowsForAccessibilityCallback callback) {
             mContext = windowManagerService.mContext;
             mWindowManagerService = windowManagerService;
             mCallback = callback;
             mHandler = new MyHandler(mWindowManagerService.mH.getLooper());
+            mRecurringAccessibilityEventsIntervalMillis = ViewConfiguration
+                    .getSendRecurringAccessibilityEventsInterval();
             computeChangedWindows();
         }
 
+        public void scheduleComputeChangedWindowsLocked() {
+            // If focus changed, compute changed windows immediately as the focused window
+            // is used by the accessibility manager service to determine the active window.
+            if (mWindowManagerService.mCurrentFocus != null
+                    && mWindowManagerService.mCurrentFocus != mWindowManagerService.mLastFocus) {
+                mHandler.removeMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS);
+                computeChangedWindows();
+            } else if (!mHandler.hasMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS)) {
+                mHandler.sendEmptyMessageDelayed(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS,
+                        mRecurringAccessibilityEventsIntervalMillis);
+            }
+        }
+
         public void computeChangedWindows() {
             if (DEBUG) {
                 Slog.i(LOG_TAG, "computeChangedWindows()");
             }
 
             synchronized (mWindowManagerService.mWindowMap) {
+                // Do not send the windows if there is no current focus as
+                // the window manager is still looking for where to put it.
+                // We will do the work when we get a focus change callback.
+                if (mWindowManagerService.mCurrentFocus == null) {
+                    return;
+                }
+
                 WindowManager windowManager = (WindowManager)
                         mContext.getSystemService(Context.WINDOW_SERVICE);
                 windowManager.getDefaultDisplay().getRealSize(mTempPoint);
@@ -912,37 +946,21 @@
                 Set<IBinder> addedWindows = mTempBinderSet;
                 addedWindows.clear();
 
+                boolean focusedWindowAdded = false;
+
                 final int visibleWindowCount = visibleWindows.size();
                 for (int i = visibleWindowCount - 1; i >= 0; i--) {
                     WindowState windowState = visibleWindows.valueAt(i);
-                    // Compute the window touchable frame as shown on the screen.
 
-                    // Get the touchable frame.
-                    Region touchableRegion = mTempRegion1;
-                    windowState.getTouchableRegion(touchableRegion);
-                    Rect touchableFrame = mTempRect;
-                    touchableRegion.getBounds(touchableFrame);
-
-                    // Move to origin as all transforms are captured by the matrix.
-                    RectF windowFrame = mTempRectF;
-                    windowFrame.set(touchableFrame);
-                    windowFrame.offset(-windowState.mFrame.left, -windowState.mFrame.top);
-
-                    // Map the frame to get what appears on the screen.
-                    Matrix matrix = mTempMatrix;
-                    populateTransformationMatrixLocked(windowState, matrix);
-                    matrix.mapRect(windowFrame);
-
-                    // Got the bounds.
+                    // Compute the bounds in the screen.
                     Rect boundsInScreen = mTempRect;
-                    boundsInScreen.set((int) windowFrame.left, (int) windowFrame.top,
-                            (int) windowFrame.right, (int) windowFrame.bottom);
+                    computeWindowBoundsInScreen(windowState, boundsInScreen);
 
                     final int flags = windowState.mAttrs.flags;
 
                     // If the window is not touchable, do not report it but take into account
                     // the space it takes since the content behind it cannot be touched.
-                    if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) == 1) {
+                    if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
                         unaccountedSpace.op(boundsInScreen, unaccountedSpace,
                                 Region.Op.DIFFERENCE);
                         continue;
@@ -956,33 +974,12 @@
                     // Add windows of certain types not covered by modal windows.
                     if (isReportedWindowType(windowState.mAttrs.type)) {
                         // Add the window to the ones to be reported.
-                        WindowInfo window = WindowInfo.obtain();
-                        window.type = windowState.mAttrs.type;
-                        window.layer = windowState.mLayer;
-                        window.token = windowState.mClient.asBinder();
-
+                        WindowInfo window = obtainPopulatedWindowInfo(windowState, boundsInScreen);
                         addedWindows.add(window.token);
-
-                        WindowState attachedWindow = windowState.mAttachedWindow;
-                        if (attachedWindow != null) {
-                            window.parentToken = attachedWindow.mClient.asBinder();
-                        }
-
-                        window.focused = windowState.isFocused();
-                        window.boundsInScreen.set(boundsInScreen);
-
-                        final int childCount = windowState.mChildWindows.size();
-                        if (childCount > 0) {
-                            if (window.childTokens == null) {
-                                window.childTokens = new ArrayList<IBinder>();
-                            }
-                            for (int j = 0; j < childCount; j++) {
-                                WindowState child = windowState.mChildWindows.get(j);
-                                window.childTokens.add(child.mClient.asBinder());
-                            }
-                        }
-
                         windows.add(window);
+                        if (windowState.isFocused()) {
+                            focusedWindowAdded = true;
+                        }
                     }
 
                     // Account for the space this window takes.
@@ -1001,6 +998,25 @@
                     }
                 }
 
+                // Always report the focused window.
+                if (!focusedWindowAdded) {
+                    for (int i = visibleWindowCount - 1; i >= 0; i--) {
+                        WindowState windowState = visibleWindows.valueAt(i);
+                        if (windowState.isFocused()) {
+                            // Compute the bounds in the screen.
+                            Rect boundsInScreen = mTempRect;
+                            computeWindowBoundsInScreen(windowState, boundsInScreen);
+
+                            // Add the window to the ones to be reported.
+                            WindowInfo window = obtainPopulatedWindowInfo(windowState,
+                                    boundsInScreen);
+                            addedWindows.add(window.token);
+                            windows.add(window);
+                            break;
+                        }
+                    }
+                }
+
                 // Remove child/parent references to windows that were not added.
                 final int windowCount = windows.size();
                 for (int i = 0; i < windowCount; i++) {
@@ -1063,6 +1079,57 @@
             }
         }
 
+        private void computeWindowBoundsInScreen(WindowState windowState, Rect outBounds) {
+            // Get the touchable frame.
+            Region touchableRegion = mTempRegion1;
+            windowState.getTouchableRegion(touchableRegion);
+            Rect touchableFrame = mTempRect;
+            touchableRegion.getBounds(touchableFrame);
+
+            // Move to origin as all transforms are captured by the matrix.
+            RectF windowFrame = mTempRectF;
+            windowFrame.set(touchableFrame);
+            windowFrame.offset(-windowState.mFrame.left, -windowState.mFrame.top);
+
+            // Map the frame to get what appears on the screen.
+            Matrix matrix = mTempMatrix;
+            populateTransformationMatrixLocked(windowState, matrix);
+            matrix.mapRect(windowFrame);
+
+            // Got the bounds.
+            outBounds.set((int) windowFrame.left, (int) windowFrame.top,
+                    (int) windowFrame.right, (int) windowFrame.bottom);
+        }
+
+        private static WindowInfo obtainPopulatedWindowInfo(WindowState windowState,
+                Rect boundsInScreen) {
+            WindowInfo window = WindowInfo.obtain();
+            window.type = windowState.mAttrs.type;
+            window.layer = windowState.mLayer;
+            window.token = windowState.mClient.asBinder();
+
+            WindowState attachedWindow = windowState.mAttachedWindow;
+            if (attachedWindow != null) {
+                window.parentToken = attachedWindow.mClient.asBinder();
+            }
+
+            window.focused = windowState.isFocused();
+            window.boundsInScreen.set(boundsInScreen);
+
+            final int childCount = windowState.mChildWindows.size();
+            if (childCount > 0) {
+                if (window.childTokens == null) {
+                    window.childTokens = new ArrayList<IBinder>();
+                }
+                for (int j = 0; j < childCount; j++) {
+                    WindowState child = windowState.mChildWindows.get(j);
+                    window.childTokens.add(child.mClient.asBinder());
+                }
+            }
+
+            return window;
+        }
+
         private void cacheWindows(List<WindowInfo> windows) {
             final int oldWindowCount = mOldWindows.size();
             for (int i = oldWindowCount - 1; i >= 0; i--) {
@@ -1079,10 +1146,10 @@
             if (oldWindow == newWindow) {
                 return false;
             }
-            if (oldWindow == null && newWindow != null) {
+            if (oldWindow == null) {
                 return true;
             }
-            if (oldWindow != null && newWindow == null) {
+            if (newWindow == null) {
                 return true;
             }
             if (oldWindow.type != newWindow.type) {
@@ -1151,7 +1218,8 @@
         }
 
         private class MyHandler extends Handler {
-            public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED = 1;
+            public static final int MESSAGE_COMPUTE_CHANGED_WINDOWS = 1;
+            public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED = 2;
 
             public MyHandler(Looper looper) {
                 super(looper, null, false);
@@ -1161,6 +1229,10 @@
             @SuppressWarnings("unchecked")
             public void handleMessage(Message message) {
                 switch (message.what) {
+                    case MESSAGE_COMPUTE_CHANGED_WINDOWS: {
+                        computeChangedWindows();
+                    } break;
+
                     case MESSAGE_NOTIFY_WINDOWS_CHANGED: {
                         List<WindowInfo> windows = (List<WindowInfo>) message.obj;
                         mCallback.onWindowsForAccessibilityChanged(windows);
diff --git a/services/core/java/com/android/server/wm/CircularDisplayMask.java b/services/core/java/com/android/server/wm/CircularDisplayMask.java
deleted file mode 100644
index 35d19c1..0000000
--- a/services/core/java/com/android/server/wm/CircularDisplayMask.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.view.Display;
-import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-
-class CircularDisplayMask {
-    private static final String TAG = "CircularDisplayMask";
-
-    private static final int STROKE_WIDTH = 2;
-
-    private final SurfaceControl mSurfaceControl;
-    private final Surface mSurface = new Surface();
-    private int mLastDW;
-    private int mLastDH;
-    private boolean mDrawNeeded;
-    private Paint mPaint;
-    private int mRotation;
-
-    public CircularDisplayMask(Display display, SurfaceSession session, int zOrder) {
-        SurfaceControl ctrl = null;
-        try {
-            ctrl = new SurfaceControl(session, "CircularDisplayMask",
-                320, 290, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
-            ctrl.setLayerStack(display.getLayerStack());
-            ctrl.setLayer(zOrder);
-            ctrl.setPosition(0, 0);
-            ctrl.show();
-            mSurface.copyFrom(ctrl);
-        } catch (OutOfResourcesException e) {
-        }
-        mSurfaceControl = ctrl;
-        mDrawNeeded = true;
-        mPaint = new Paint();
-        mPaint.setAntiAlias(true);
-        mPaint.setStyle(Paint.Style.STROKE);
-        mPaint.setColor(Color.BLACK);
-        mPaint.setStrokeWidth(STROKE_WIDTH);
-    }
-
-    private void drawIfNeeded() {
-        if (!mDrawNeeded) {
-            return;
-        }
-        mDrawNeeded = false;
-
-        Rect dirty = new Rect(0, 0, mLastDW, mLastDH);
-        Canvas c = null;
-        try {
-            c = mSurface.lockCanvas(dirty);
-        } catch (IllegalArgumentException e) {
-        } catch (Surface.OutOfResourcesException e) {
-        }
-        if (c == null) {
-            return;
-        }
-        int cx = 160;
-        int cy = 160;
-        switch (mRotation) {
-            case Surface.ROTATION_0:
-            case Surface.ROTATION_90:
-                // chin bottom or right
-                cx = 160;
-                cy = 160;
-                break;
-            case Surface.ROTATION_180:
-                // chin top
-                cx = 160;
-                cy = 145;
-                break;
-            case Surface.ROTATION_270:
-                cx = 145;
-                cy = 160;
-                break;
-        }
-        c.drawCircle(cx, cy, 160, mPaint);
-
-        mSurface.unlockCanvasAndPost(c);
-    }
-
-    // Note: caller responsible for being inside
-    // Surface.openTransaction() / closeTransaction()
-    public void setVisibility(boolean on) {
-        if (mSurfaceControl == null) {
-            return;
-        }
-        drawIfNeeded();
-        if (on) {
-            mSurfaceControl.show();
-        } else {
-            mSurfaceControl.hide();
-        }
-    }
-
-    void positionSurface(int dw, int dh, int rotation) {
-        if (mLastDW == dw && mLastDH == dh) {
-            return;
-        }
-        mLastDW = dw;
-        mLastDH = dh;
-        mSurfaceControl.setSize(dw, dh);
-        mDrawNeeded = true;
-        mRotation = rotation;
-    }
-
-}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 3200b54..b4cf2ae 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -446,11 +446,11 @@
         }
     }
 
-    public void onRectangleOnScreenRequested(IBinder token, Rect rectangle, boolean immediate) {
+    public void onRectangleOnScreenRequested(IBinder token, Rect rectangle) {
         synchronized(mService.mWindowMap) {
             final long identity = Binder.clearCallingIdentity();
             try {
-                mService.onRectangleOnScreenRequested(token, rectangle, immediate);
+                mService.onRectangleOnScreenRequested(token, rectangle);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1db8b55..76085fa 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -151,7 +151,6 @@
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 
@@ -433,7 +432,6 @@
     final SurfaceSession mFxSession;
     Watermark mWatermark;
     StrictModeFlash mStrictModeFlash;
-    CircularDisplayMask mCircularDisplayMask;
     FocusedStackFrame mFocusedStackFrame;
 
     int mFocusedStackLayer;
@@ -846,8 +844,6 @@
         } finally {
             SurfaceControl.closeTransaction();
         }
-
-        showCircularDisplayMaskIfNeeded();
     }
 
     public InputMonitor getInputMonitor() {
@@ -1487,10 +1483,15 @@
 
         if (pos >= 0) {
             final AppWindowToken targetAppToken = mInputMethodTarget.mAppToken;
-            if (pos < windows.size()) {
-                WindowState wp = windows.get(pos);
-                if (wp == mInputMethodWindow) {
-                    pos++;
+            // Skip windows owned by the input method.
+            if (mInputMethodWindow != null) {
+                while (pos < windows.size()) {
+                    WindowState wp = windows.get(pos);
+                    if (wp == mInputMethodWindow || wp.mAttachedWindow == mInputMethodWindow) {
+                        pos++;
+                        continue;
+                    }
+                    break;
                 }
             }
             if (DEBUG_INPUT_METHOD) Slog.v(TAG, "Adding " + N + " dialogs at pos=" + pos);
@@ -2810,14 +2811,13 @@
         performLayoutAndPlaceSurfacesLocked();
     }
 
-    public void onRectangleOnScreenRequested(IBinder token, Rect rectangle, boolean immediate) {
+    public void onRectangleOnScreenRequested(IBinder token, Rect rectangle) {
         synchronized (mWindowMap) {
             if (mAccessibilityController != null) {
                 WindowState window = mWindowMap.get(token);
                 //TODO (multidisplay): Magnification is supported only for the default display.
                 if (window != null && window.getDisplayId() == Display.DEFAULT_DISPLAY) {
-                    mAccessibilityController.onRectangleOnScreenRequestedLocked(rectangle,
-                            immediate);
+                    mAccessibilityController.onRectangleOnScreenRequestedLocked(rectangle);
                 }
             }
         }
@@ -5574,39 +5574,6 @@
         }
     }
 
-    public void showCircularDisplayMaskIfNeeded() {
-        // we're fullscreen and not hosted in an ActivityView
-        if (mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_windowIsRound)) {
-            mH.sendMessage(mH.obtainMessage(H.SHOW_DISPLAY_MASK));
-        }
-    }
-
-    public void showCircularMask() {
-        synchronized(mWindowMap) {
-
-            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
-                    ">>> OPEN TRANSACTION showDisplayMask");
-            SurfaceControl.openTransaction();
-            try {
-                // TODO(multi-display): support multiple displays
-                if (mCircularDisplayMask == null) {
-                    mCircularDisplayMask = new CircularDisplayMask(
-                            getDefaultDisplayContentLocked().getDisplay(),
-                            mFxSession,
-                            mPolicy.windowTypeToLayerLw(
-                                    WindowManager.LayoutParams.TYPE_POINTER)
-                                    * TYPE_LAYER_MULTIPLIER + 10);
-                }
-                mCircularDisplayMask.setVisibility(true);
-            } finally {
-                SurfaceControl.closeTransaction();
-                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
-                        "<<< CLOSE TRANSACTION showDisplayMask");
-            }
-        }
-    }
-
     // TODO: more accounting of which pid(s) turned it on, keep count,
     // only allow disables from pids which have count on, etc.
     @Override
@@ -5864,7 +5831,9 @@
                 // whether the screenshot should use the identity transformation matrix
                 // (e.g., enable it when taking a screenshot for recents, since we might be in
                 // the middle of the rotation animation, but don't want a rotated recent image).
-                rawss = SurfaceControl.screenshot(dw, dh, minLayer, maxLayer, false);
+                // TODO: Replace 'new Rect()' with the portion of the screen to capture for the
+                // screenshot.
+                rawss = SurfaceControl.screenshot(new Rect(), dw, dh, minLayer, maxLayer, false);
             }
         } while (!screenshotReady && retryCount <= MAX_SCREENSHOT_RETRIES);
         if (retryCount > MAX_SCREENSHOT_RETRIES)  Slog.i(TAG, "Screenshot max retries " +
@@ -7211,10 +7180,9 @@
         public static final int TAP_OUTSIDE_STACK = 31;
         public static final int NOTIFY_ACTIVITY_DRAWN = 32;
 
-        public static final int SHOW_DISPLAY_MASK = 33;
-        public static final int ALL_WINDOWS_DRAWN = 34;
+        public static final int ALL_WINDOWS_DRAWN = 33;
 
-        public static final int NEW_ANIMATOR_SCALE = 35;
+        public static final int NEW_ANIMATOR_SCALE = 34;
 
         @Override
         public void handleMessage(Message msg) {
@@ -7613,11 +7581,6 @@
                     break;
                 }
 
-                case SHOW_DISPLAY_MASK: {
-                    showCircularMask();
-                    break;
-                }
-
                 case DO_ANIMATION_CALLBACK: {
                     try {
                         ((IRemoteCallback)msg.obj).sendResult(null);
@@ -9133,9 +9096,6 @@
             if (mStrictModeFlash != null) {
                 mStrictModeFlash.positionSurface(defaultDw, defaultDh);
             }
-            if (mCircularDisplayMask != null) {
-                mCircularDisplayMask.positionSurface(defaultDw, defaultDh, mRotation);
-            }
 
             boolean focusDisplayed = false;
 
@@ -9266,6 +9226,13 @@
                         winAnimator.setAnimation(a);
                         winAnimator.mAnimDw = w.mLastFrame.left - w.mFrame.left;
                         winAnimator.mAnimDh = w.mLastFrame.top - w.mFrame.top;
+
+                        //TODO (multidisplay): Accessibility supported only for the default display.
+                        if (mAccessibilityController != null
+                                && displayId == Display.DEFAULT_DISPLAY) {
+                            mAccessibilityController.onSomeWindowResizedOrMovedLocked();
+                        }
+
                         try {
                             w.mClient.moved(w.mFrame.left, w.mFrame.top);
                         } catch (RemoteException e) {
@@ -9904,7 +9871,9 @@
             mCurrentFocus = newFocus;
             mLosingFocus.remove(newFocus);
 
-            if (mAccessibilityController != null) {
+            // TODO(multidisplay): Accessibilty supported only of default desiplay.
+            if (mAccessibilityController != null
+                    && displayContent.getDisplayId() == Display.DEFAULT_DISPLAY) {
                 mAccessibilityController.onWindowFocusChangedLocked();
             }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4a80e3e..97178bd 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -36,6 +36,7 @@
 import android.os.RemoteCallbackList;
 import android.os.SystemClock;
 import android.util.TimeUtils;
+import android.view.Display;
 import android.view.IWindowFocusObserver;
 import android.view.IWindowId;
 import com.android.server.input.InputWindowHandle;
@@ -1359,6 +1360,13 @@
                 mClient.resized(frame, overscanInsets, contentInsets, visibleInsets, reportDraw,
                         newConfig);
             }
+
+            //TODO (multidisplay): Accessibility supported only for the default display.
+            if (mService.mAccessibilityController != null
+                    && getDisplayId() == Display.DEFAULT_DISPLAY) {
+                mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
+            }
+
             mOverscanInsetsChanged = false;
             mContentInsetsChanged = false;
             mVisibleInsetsChanged = false;
diff --git a/services/core/jni/com_android_server_AssetAtlasService.cpp b/services/core/jni/com_android_server_AssetAtlasService.cpp
index 163225e..9a5079d 100644
--- a/services/core/jni/com_android_server_AssetAtlasService.cpp
+++ b/services/core/jni/com_android_server_AssetAtlasService.cpp
@@ -18,6 +18,7 @@
 
 #include "jni.h"
 #include "JNIHelp.h"
+#include "android/graphics/GraphicsJNI.h"
 
 #include <android_view_GraphicBuffer.h>
 #include <cutils/log.h>
@@ -46,7 +47,7 @@
 // ----------------------------------------------------------------------------
 
 static struct {
-    jmethodID safeCanvasSwap;
+    jmethodID setNativeBitmap;
 } gCanvasClassInfo;
 
 #define INVOKEV(object, method, ...) \
@@ -63,9 +64,7 @@
     bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
     bitmap->allocPixels();
     bitmap->eraseColor(0);
-
-    SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (*bitmap));
-    INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false);
+    INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, reinterpret_cast<jlong>(bitmap));
 
     return reinterpret_cast<jlong>(bitmap);
 }
@@ -74,8 +73,7 @@
         jobject canvas, jlong bitmapHandle) {
 
     SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
-    SkCanvas* nativeCanvas = SkNEW(SkCanvas);
-    INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false);
+    INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, (jlong)0);
 
     delete bitmap;
 }
@@ -244,7 +242,7 @@
     jclass clazz;
 
     FIND_CLASS(clazz, "android/graphics/Canvas");
-    GET_METHOD_ID(gCanvasClassInfo.safeCanvasSwap, clazz, "safeCanvasSwap", "(JZ)V");
+    GET_METHOD_ID(gCanvasClassInfo.setNativeBitmap, clazz, "setNativeBitmap", "(J)V");
 
     return jniRegisterNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
 }
diff --git a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
index a734026..cbc853d 100644
--- a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
+++ b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
@@ -57,6 +57,12 @@
     int getVersion();
     // Get vendor id used for vendor command.
     uint32_t getVendorId();
+    // Set a flag and its value.
+    void setOption(int flag, int value);
+    // Set audio return channel status.
+    void setAudioReturnChannel(bool flag);
+    // Whether to hdmi device is connected to the given port.
+    bool isConnected(int port);
 
     jobject getCallbacksObj() const {
         return mCallbacksObj;
@@ -222,6 +228,20 @@
     return vendorId;
 }
 
+void HdmiCecController::setOption(int flag, int value) {
+    mDevice->set_option(mDevice, flag, value);
+}
+
+// Set audio return channel status.
+void HdmiCecController::setAudioReturnChannel(bool enabled) {
+    mDevice->set_audio_return_channel(mDevice, enabled ? 1 : 0);
+}
+
+// Whether to hdmi device is connected to the given port.
+bool HdmiCecController::isConnected(int port) {
+    return mDevice->is_connected(mDevice, port) == HDMI_CONNECTED;
+}
+
 
 // static
 void HdmiCecController::onReceived(const hdmi_event_t* event, void* arg) {
@@ -326,6 +346,26 @@
     return controller->getVendorId();
 }
 
+static void nativeSetOption(JNIEnv* env, jclass clazz, jlong controllerPtr, jint flag,
+        jint value) {
+    HdmiCecController* controller =
+            reinterpret_cast<HdmiCecController*>(controllerPtr);
+    controller->setOption(flag, value);
+}
+
+static void nativeSetAudioReturnChannel(JNIEnv* env, jclass clazz, jlong controllerPtr,
+        jboolean enabled) {
+    HdmiCecController* controller =
+            reinterpret_cast<HdmiCecController*>(controllerPtr);
+    controller->setAudioReturnChannel(enabled == JNI_TRUE);
+}
+
+static jboolean nativeIsConnected(JNIEnv* env, jclass clazz, jlong controllerPtr, jint port) {
+    HdmiCecController* controller =
+                reinterpret_cast<HdmiCecController*>(controllerPtr);
+    return controller->isConnected(port) ? JNI_TRUE : JNI_FALSE ;
+}
+
 static JNINativeMethod sMethods[] = {
     /* name, signature, funcPtr */
     { "nativeInit",
@@ -337,6 +377,9 @@
     { "nativeGetPhysicalAddress", "(J)I", (void *) nativeGetPhysicalAddress },
     { "nativeGetVersion", "(J)I", (void *) nativeGetVersion },
     { "nativeGetVendorId", "(J)I", (void *) nativeGetVendorId },
+    { "nativeSetOption", "(JII)V", (void *) nativeSetOption },
+    { "nativeSetAudioReturnChannel", "(JZ)V", (void *) nativeSetAudioReturnChannel },
+    { "nativeIsConnected", "(JI)Z", (void *) nativeIsConnected },
 };
 
 #define CLASS_PATH "com/android/server/hdmi/HdmiCecController"
diff --git a/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp b/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp
index 6c14887..c2fccc1 100644
--- a/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp
+++ b/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp
@@ -139,6 +139,8 @@
  * the HW module and obtaining the proper interfaces.
  */
 static void ClassInit(JNIEnv* env, jclass clazz) {
+  sFlpInterface = NULL;
+
   // get references to the Java provider methods
   sOnLocationReport = env->GetMethodID(
       clazz,
@@ -163,6 +165,38 @@
   sOnGeofenceRemove = env->GetMethodID(clazz, "onGeofenceRemove", "(II)V");
   sOnGeofencePause = env->GetMethodID(clazz, "onGeofencePause", "(II)V");
   sOnGeofenceResume = env->GetMethodID(clazz, "onGeofenceResume", "(II)V");
+
+  // open the hardware module
+  const hw_module_t* module = NULL;
+  int err = hw_get_module(FUSED_LOCATION_HARDWARE_MODULE_ID, &module);
+  if (err != 0) {
+    ALOGE("Error hw_get_module '%s': %d", FUSED_LOCATION_HARDWARE_MODULE_ID, err);
+    return;
+  }
+
+  err = module->methods->open(
+      module,
+      FUSED_LOCATION_HARDWARE_MODULE_ID,
+      &sHardwareDevice);
+  if (err != 0) {
+    ALOGE("Error opening device '%s': %d", FUSED_LOCATION_HARDWARE_MODULE_ID, err);
+    return;
+  }
+
+  // acquire the interfaces pointers
+  flp_device_t* flp_device = reinterpret_cast<flp_device_t*>(sHardwareDevice);
+  sFlpInterface = flp_device->get_flp_interface(flp_device);
+
+  if (sFlpInterface != NULL) {
+    sFlpDiagnosticInterface = reinterpret_cast<const FlpDiagnosticInterface*>(
+        sFlpInterface->get_extension(FLP_DIAGNOSTIC_INTERFACE));
+
+    sFlpGeofencingInterface = reinterpret_cast<const FlpGeofencingInterface*>(
+        sFlpInterface->get_extension(FLP_GEOFENCING_INTERFACE));
+
+    sFlpDeviceContextInterface = reinterpret_cast<const FlpDeviceContextInterface*>(
+        sFlpInterface->get_extension(FLP_DEVICE_CONTEXT_INTERFACE));
+  }
 }
 
 /*
@@ -637,44 +671,6 @@
  * the Flp interfaces are initialized properly.
  */
 static void Init(JNIEnv* env, jobject obj) {
-  if(sHardwareDevice != NULL) {
-    ALOGD("Hardware Device already opened.");
-    return;
-  }
-
-  const hw_module_t* module = NULL;
-  int err = hw_get_module(FUSED_LOCATION_HARDWARE_MODULE_ID, &module);
-  if(err != 0) {
-    ALOGE("Error hw_get_module '%s': %d", FUSED_LOCATION_HARDWARE_MODULE_ID, err);
-    return;
-  }
-
-  err = module->methods->open(
-        module,
-        FUSED_LOCATION_HARDWARE_MODULE_ID, &sHardwareDevice);
-  if(err != 0) {
-    ALOGE("Error opening device '%s': %d", FUSED_LOCATION_HARDWARE_MODULE_ID, err);
-    return;
-  }
-
-  sFlpInterface = NULL;
-  flp_device_t* flp_device = reinterpret_cast<flp_device_t*>(sHardwareDevice);
-  sFlpInterface = flp_device->get_flp_interface(flp_device);
-
-  if(sFlpInterface != NULL) {
-    sFlpDiagnosticInterface = reinterpret_cast<const FlpDiagnosticInterface*>(
-        sFlpInterface->get_extension(FLP_DIAGNOSTIC_INTERFACE)
-        );
-
-    sFlpGeofencingInterface = reinterpret_cast<const FlpGeofencingInterface*>(
-        sFlpInterface->get_extension(FLP_GEOFENCING_INTERFACE)
-        );
-
-    sFlpDeviceContextInterface = reinterpret_cast<const FlpDeviceContextInterface*>(
-        sFlpInterface->get_extension(FLP_DEVICE_CONTEXT_INTERFACE)
-        );
-  }
-
   if(sCallbacksObj == NULL) {
     sCallbacksObj = env->NewGlobalRef(obj);
   }
@@ -696,7 +692,10 @@
 }
 
 static jboolean IsSupported(JNIEnv* env, jclass clazz) {
-  return sFlpInterface != NULL;
+  if (sFlpInterface == NULL) {
+    return JNI_FALSE;
+  }
+  return JNI_TRUE;
 }
 
 static jint GetBatchSize(JNIEnv* env, jobject object) {
diff --git a/services/core/jni/com_android_server_location_GpsLocationProvider.cpp b/services/core/jni/com_android_server_location_GpsLocationProvider.cpp
index e9ba116..5bafb52 100644
--- a/services/core/jni/com_android_server_location_GpsLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GpsLocationProvider.cpp
@@ -30,6 +30,8 @@
 
 #include <string.h>
 #include <pthread.h>
+#include <linux/in.h>
+#include <linux/in6.h>
 
 static jobject mCallbacksObj = NULL;
 
@@ -168,19 +170,98 @@
     create_thread_callback,
 };
 
+static jbyteArray convert_to_ipv4(uint32_t ip, bool net_order)
+{
+    if (INADDR_NONE == ip) {
+        return NULL;
+    }
+
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jbyteArray byteArray = env->NewByteArray(4);
+    if (byteArray == NULL) {
+        ALOGE("Unable to allocate byte array for IPv4 address");
+        return NULL;
+    }
+
+    jbyte ipv4[4];
+    if (net_order) {
+        memcpy(ipv4, &ip, sizeof(ipv4));
+    } else {
+        //endianess transparent conversion from int to char[]
+        ipv4[0] = (jbyte) (ip & 0xFF);
+        ipv4[1] = (jbyte)((ip>>8) & 0xFF);
+        ipv4[2] = (jbyte)((ip>>16) & 0xFF);
+        ipv4[3] = (jbyte) (ip>>24);
+    }
+
+    env->SetByteArrayRegion(byteArray, 0, 4, (const jbyte*) ipv4);
+    return byteArray;
+}
+
 static void agps_status_callback(AGpsStatus* agps_status)
 {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jbyteArray byteArray = NULL;
+    bool isSupported = false;
 
-    uint32_t ipaddr;
-    // ipaddr field was not included in original AGpsStatus
-    if (agps_status->size >= sizeof(AGpsStatus))
-        ipaddr = agps_status->ipaddr;
-    else
-        ipaddr = 0xFFFFFFFF;
-    env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus,
-                        agps_status->type, agps_status->status, ipaddr);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    size_t status_size = agps_status->size;
+    if (status_size == sizeof(AGpsStatus_v3)) {
+      switch (agps_status->addr.ss_family)
+      {
+      case AF_INET:
+          {
+            struct sockaddr_in *in = (struct sockaddr_in*)&(agps_status->addr);
+            uint32_t *pAddr = (uint32_t*)&(in->sin_addr);
+            byteArray = convert_to_ipv4(*pAddr, true /* net_order */);
+            if (byteArray != NULL) {
+                isSupported = true;
+            }
+          }
+          break;
+      case AF_INET6:
+          {
+            struct sockaddr_in6 *in6 = (struct sockaddr_in6*)&(agps_status->addr);
+            byteArray = env->NewByteArray(16);
+            if (byteArray != NULL) {
+                env->SetByteArrayRegion(byteArray, 0, 16, (const jbyte *)&(in6->sin6_addr));
+                isSupported = true;
+            } else {
+                ALOGE("Unable to allocate byte array for IPv6 address.");
+            }
+          }
+          break;
+      default:
+          ALOGE("Invalid ss_family found: %d", agps_status->addr.ss_family);
+          break;
+      }
+    } else if (status_size >= sizeof(AGpsStatus_v2)) {
+      // for back-compatibility reasons we check in v2 that the data structure size is greater or
+      // equal to the declared size in gps.h
+      uint32_t ipaddr = agps_status->ipaddr;
+      byteArray = convert_to_ipv4(ipaddr, false /* net_order */);
+      if (ipaddr == INADDR_NONE || byteArray != NULL) {
+          isSupported = true;
+      }
+    } else if (status_size >= sizeof(AGpsStatus_v1)) {
+        // because we have to check for >= with regards to v2, we also need to relax the check here
+        // and only make sure that the size is at least what we expect
+        isSupported = true;
+    } else {
+        ALOGE("Invalid size of AGpsStatus found: %d.", status_size);
+    }
+
+    if (isSupported) {
+        env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus, agps_status->type,
+                            agps_status->status, byteArray);
+
+        checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    } else {
+        ALOGD("Skipping calling method_reportAGpsStatus.");
+    }
+
+    if (byteArray) {
+        env->DeleteLocalRef(byteArray);
+    }
 }
 
 AGpsCallbacks sAGpsCallbacks = {
@@ -339,7 +420,7 @@
     method_reportLocation = env->GetMethodID(clazz, "reportLocation", "(IDDDFFFJ)V");
     method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V");
     method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "()V");
-    method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(III)V");
+    method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(II[B)V");
     method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");
     method_setEngineCapabilities = env->GetMethodID(clazz, "setEngineCapabilities", "(I)V");
     method_xtraDownloadRequest = env->GetMethodID(clazz, "xtraDownloadRequest", "()V");
@@ -610,7 +691,8 @@
     env->ReleasePrimitiveArrayCritical(data, bytes, JNI_ABORT);
 }
 
-static void android_location_GpsLocationProvider_agps_data_conn_open(JNIEnv* env, jobject obj, jstring apn)
+static void android_location_GpsLocationProvider_agps_data_conn_open(
+        JNIEnv* env, jobject obj, jstring apn, jint apnIpType)
 {
     if (!sAGpsInterface) {
         ALOGE("no AGPS interface in agps_data_conn_open");
@@ -620,8 +702,18 @@
         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
         return;
     }
+
     const char *apnStr = env->GetStringUTFChars(apn, NULL);
-    sAGpsInterface->data_conn_open(apnStr);
+
+    size_t interface_size = sAGpsInterface->size;
+    if (interface_size == sizeof(AGpsInterface_v2)) {
+        sAGpsInterface->data_conn_open_with_apn_ip_type(apnStr, apnIpType);
+    } else if (interface_size == sizeof(AGpsInterface_v1)) {
+        sAGpsInterface->data_conn_open(apnStr);
+    } else {
+        ALOGE("Invalid size of AGpsInterface found: %d.", interface_size);
+    }
+
     env->ReleaseStringUTFChars(apn, apnStr);
 }
 
@@ -775,7 +867,7 @@
     {"native_inject_location", "(DDF)V", (void*)android_location_GpsLocationProvider_inject_location},
     {"native_supports_xtra", "()Z", (void*)android_location_GpsLocationProvider_supports_xtra},
     {"native_inject_xtra_data", "([BI)V", (void*)android_location_GpsLocationProvider_inject_xtra_data},
-    {"native_agps_data_conn_open", "(Ljava/lang/String;)V", (void*)android_location_GpsLocationProvider_agps_data_conn_open},
+    {"native_agps_data_conn_open", "(Ljava/lang/String;I)V", (void*)android_location_GpsLocationProvider_agps_data_conn_open},
     {"native_agps_data_conn_closed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_closed},
     {"native_agps_data_conn_failed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_failed},
     {"native_agps_set_id","(ILjava/lang/String;)V",(void*)android_location_GpsLocationProvider_agps_set_id},
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
index 674c6f4..f1284d80 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
@@ -120,6 +120,10 @@
         mDeviceOwner = new OwnerInfo(ownerName, packageName);
     }
 
+    void clearDeviceOwner() {
+        mDeviceOwner = null;
+    }
+
     void setProfileOwner(String packageName, String ownerName, int userId) {
         mProfileOwners.put(userId, new OwnerInfo(ownerName, packageName));
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 5395f60..4574caf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -32,6 +32,7 @@
 import android.app.ActivityManagerNative;
 import android.app.AlarmManager;
 import android.app.AppGlobals;
+import android.app.IActivityManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -51,6 +52,10 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
+import android.media.AudioManager;
+import android.media.IAudioService;
+import android.net.ConnectivityManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.net.ProxyInfo;
 import android.os.Binder;
 import android.os.Bundle;
@@ -130,6 +135,8 @@
 
     private static final boolean DBG = false;
 
+    private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
+
     final Context mContext;
     final UserManager mUserManager;
     final PowerManager.WakeLock mWakeLock;
@@ -190,6 +197,8 @@
         // This is the list of component allowed to start lock task mode.
         final List<ComponentName> mLockTaskComponents = new ArrayList<ComponentName>();
 
+        ComponentName mRestrictionsProvider;
+
         public DevicePolicyData(int userHandle) {
             mUserHandle = userHandle;
         }
@@ -810,12 +819,20 @@
         }
 
         if (who != null) {
+            if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
+                throw new SecurityException("Admin " + candidates.get(0).info.getComponent()
+                         + " does not own the device");
+            }
+            if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
+                throw new SecurityException("Admin " + candidates.get(0).info.getComponent()
+                        + " does not own the profile");
+            }
             throw new SecurityException("Admin " + candidates.get(0).info.getComponent()
                     + " did not specify uses-policy for: "
                     + candidates.get(0).info.getTagForPolicy(reqPolicy));
         } else {
             throw new SecurityException("No active admin owned by uid "
-                    + Binder.getCallingUid() + " for policy:" + reqPolicy);
+                    + Binder.getCallingUid() + " for policy #" + reqPolicy);
         }
     }
 
@@ -823,20 +840,28 @@
         sendAdminCommandLocked(admin, action, null);
     }
 
+    void sendAdminCommandLocked(ActiveAdmin admin, String action, BroadcastReceiver result) {
+        sendAdminCommandLocked(admin, action, null, result);
+    }
+
     /**
      * Send an update to one specific admin, get notified when that admin returns a result.
      */
-    void sendAdminCommandLocked(ActiveAdmin admin, String action, BroadcastReceiver result) {
+    void sendAdminCommandLocked(ActiveAdmin admin, String action, Bundle adminExtras,
+            BroadcastReceiver result) {
         Intent intent = new Intent(action);
         intent.setComponent(admin.info.getComponent());
         if (action.equals(DeviceAdminReceiver.ACTION_PASSWORD_EXPIRING)) {
             intent.putExtra("expiration", admin.passwordExpirationDate);
         }
+        if (adminExtras != null) {
+            intent.putExtras(adminExtras);
+        }
         if (result != null) {
             mContext.sendOrderedBroadcastAsUser(intent, admin.getUserHandle(),
                     null, result, mHandler, Activity.RESULT_OK, null, null);
         } else {
-            mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
+            mContext.sendBroadcastAsUser(intent, admin.getUserHandle());
         }
     }
 
@@ -944,6 +969,10 @@
             out.startDocument(null, true);
 
             out.startTag(null, "policies");
+            if (policy.mRestrictionsProvider != null) {
+                out.attribute(null, ATTR_PERMISSION_PROVIDER,
+                        policy.mRestrictionsProvider.flattenToString());
+            }
 
             final int N = policy.mAdminList.size();
             for (int i=0; i<N; i++) {
@@ -1039,6 +1068,13 @@
                 throw new XmlPullParserException(
                         "Settings do not start with policies tag: found " + tag);
             }
+
+            // Extract the permission provider component name if available
+            String permissionProvider = parser.getAttributeValue(null, ATTR_PERMISSION_PROVIDER);
+            if (permissionProvider != null) {
+                policy.mRestrictionsProvider = ComponentName.unflattenFromString(permissionProvider);
+            }
+
             type = parser.next();
             int outerDepth = parser.getDepth();
             policy.mLockTaskComponents.clear();
@@ -1212,6 +1248,7 @@
             loadSettingsLocked(getUserData(UserHandle.USER_OWNER), UserHandle.USER_OWNER);
             loadDeviceOwner();
         }
+        cleanUpOldUsers();
         mAppOpsService = IAppOpsService.Stub.asInterface(
                 ServiceManager.getService(Context.APP_OPS_SERVICE));
         if (mDeviceOwner != null) {
@@ -1232,6 +1269,32 @@
         }
     }
 
+    private void cleanUpOldUsers() {
+        // This is needed in case the broadcast {@link Intent.ACTION_USER_REMOVED} was not handled
+        // before reboot
+        Set<Integer> usersWithProfileOwners;
+        Set<Integer> usersWithData;
+        synchronized(this) {
+            usersWithProfileOwners = mDeviceOwner != null
+                    ? mDeviceOwner.getProfileOwnerKeys() : new HashSet<Integer>();
+            usersWithData = new HashSet<Integer>();
+            for (int i = 0; i < mUserData.size(); i++) {
+                usersWithData.add(mUserData.keyAt(i));
+            }
+        }
+        List<UserInfo> allUsers = mUserManager.getUsers();
+
+        Set<Integer> deletedUsers = new HashSet<Integer>();
+        deletedUsers.addAll(usersWithProfileOwners);
+        deletedUsers.addAll(usersWithData);
+        for (UserInfo userInfo : allUsers) {
+            deletedUsers.remove(userInfo.id);
+        }
+        for (Integer userId : deletedUsers) {
+            removeUserData(userId);
+        }
+    }
+
     private void handlePasswordExpirationNotification(int userHandle) {
         synchronized (this) {
             final long now = System.currentTimeMillis();
@@ -1321,6 +1384,11 @@
         if (!mHasFeature) {
             return;
         }
+        setActiveAdmin(adminReceiver, refreshing, userHandle, null);
+    }
+
+    private void setActiveAdmin(ComponentName adminReceiver, boolean refreshing, int userHandle,
+            Bundle onEnableData) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.MANAGE_DEVICE_ADMINS, null);
         enforceCrossUserPermission(userHandle);
@@ -1357,7 +1425,8 @@
                     policy.mAdminList.set(replaceIndex, newAdmin);
                 }
                 saveSettingsLocked(userHandle);
-                sendAdminCommandLocked(newAdmin, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED);
+                sendAdminCommandLocked(newAdmin, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED,
+                        onEnableData, null);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2680,6 +2749,20 @@
         return null;
     }
 
+    public void setRecommendedGlobalProxy(ComponentName who, ProxyInfo proxyInfo) {
+        synchronized (this) {
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+        }
+        long token = Binder.clearCallingIdentity();
+        try {
+            ConnectivityManager connectivityManager = (ConnectivityManager)
+                    mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+            connectivityManager.setGlobalProxy(proxyInfo);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     private void resetGlobalProxyLocked(DevicePolicyData policy) {
         final int N = policy.mAdminList.size();
         for (int i = 0; i < N; i++) {
@@ -3041,6 +3124,37 @@
     }
 
     @Override
+    public void clearDeviceOwner(String packageName) {
+        if (packageName == null) {
+            throw new NullPointerException("packageName is null");
+        }
+        try {
+            int uid = mContext.getPackageManager().getPackageUid(packageName, 0);
+            if (uid != Binder.getCallingUid()) {
+                throw new SecurityException("Invalid packageName");
+            }
+        } catch (NameNotFoundException e) {
+            throw new SecurityException(e);
+        }
+        if (!isDeviceOwner(packageName)) {
+            throw new SecurityException("clearDeviceOwner can only be called by the device owner");
+        }
+        synchronized (this) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                mUserManager.setUserRestrictions(new Bundle(),
+                        new UserHandle(UserHandle.USER_OWNER));
+                if (mDeviceOwner != null) {
+                    mDeviceOwner.clearDeviceOwner();
+                    mDeviceOwner.writeOwnerFile();
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
     public boolean setProfileOwner(String packageName, String ownerName, int userHandle) {
         if (!mHasFeature) {
             return false;
@@ -3108,6 +3222,7 @@
                 intent.putExtra(Intent.EXTRA_USER, new UserHandle(UserHandle.getCallingUserId()));
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY |
                         Intent.FLAG_RECEIVER_FOREGROUND);
+                // TODO This should send to parent of profile (which is always owner at the moment).
                 mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
             } finally {
                 restoreCallingIdentity(id);
@@ -3303,7 +3418,33 @@
         }
     }
 
-    public void addForwardingIntentFilter(ComponentName who, IntentFilter filter, int flags) {
+    @Override
+    public void setRestrictionsProvider(ComponentName who, ComponentName permissionProvider) {
+        synchronized (this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+            int userHandle = UserHandle.getCallingUserId();
+            DevicePolicyData userData = getUserData(userHandle);
+            userData.mRestrictionsProvider = permissionProvider;
+            saveSettingsLocked(userHandle);
+        }
+    }
+
+    @Override
+    public ComponentName getRestrictionsProvider(int userHandle) {
+        synchronized (this) {
+            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+                throw new SecurityException("Only the system can query the permission provider");
+            }
+            DevicePolicyData userData = getUserData(userHandle);
+            return userData != null ? userData.mRestrictionsProvider : null;
+        }
+    }
+
+    public void addCrossProfileIntentFilter(ComponentName who, IntentFilter filter, int flags) {
         int callingUserId = UserHandle.getCallingUserId();
         synchronized (this) {
             if (who == null) {
@@ -3314,12 +3455,12 @@
             IPackageManager pm = AppGlobals.getPackageManager();
             long id = Binder.clearCallingIdentity();
             try {
-                if ((flags & DevicePolicyManager.FLAG_TO_PRIMARY_USER) != 0) {
-                    pm.addForwardingIntentFilter(filter, true /*removable*/, callingUserId,
+                if ((flags & DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED) != 0) {
+                    pm.addCrossProfileIntentFilter(filter, true /*removable*/, callingUserId,
                             UserHandle.USER_OWNER);
                 }
-                if ((flags & DevicePolicyManager.FLAG_TO_MANAGED_PROFILE) != 0) {
-                    pm.addForwardingIntentFilter(filter, true /*removable*/, UserHandle.USER_OWNER,
+                if ((flags & DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT) != 0) {
+                    pm.addCrossProfileIntentFilter(filter, true /*removable*/, UserHandle.USER_OWNER,
                             callingUserId);
                 }
             } catch (RemoteException re) {
@@ -3330,7 +3471,7 @@
         }
     }
 
-    public void clearForwardingIntentFilters(ComponentName who) {
+    public void clearCrossProfileIntentFilters(ComponentName who) {
         int callingUserId = UserHandle.getCallingUserId();
         synchronized (this) {
             if (who == null) {
@@ -3340,8 +3481,8 @@
             IPackageManager pm = AppGlobals.getPackageManager();
             long id = Binder.clearCallingIdentity();
             try {
-                pm.clearForwardingIntentFilters(callingUserId);
-                pm.clearForwardingIntentFilters(UserHandle.USER_OWNER);
+                pm.clearCrossProfileIntentFilters(callingUserId);
+                pm.clearCrossProfileIntentFilters(UserHandle.USER_OWNER);
             } catch (RemoteException re) {
                 // Shouldn't happen
             } finally {
@@ -3372,6 +3513,36 @@
     }
 
     @Override
+    public UserHandle createAndInitializeUser(ComponentName who, String name,
+            String ownerName, ComponentName profileOwnerComponent, Bundle adminExtras) {
+        UserHandle user = createUser(who, name);
+        long id = Binder.clearCallingIdentity();
+        try {
+            String profileOwnerPkg = profileOwnerComponent.getPackageName();
+            final IPackageManager ipm = AppGlobals.getPackageManager();
+            IActivityManager activityManager = ActivityManagerNative.getDefault();
+
+            try {
+                // Install the profile owner if not present.
+                if (!ipm.isPackageAvailable(profileOwnerPkg, user.getIdentifier())) {
+                    ipm.installExistingPackageAsUser(profileOwnerPkg, user.getIdentifier());
+                }
+
+                // Start user in background.
+                activityManager.startUserInBackground(user.getIdentifier());
+            } catch (RemoteException e) {
+                Slog.e(LOG_TAG, "Failed to make remote calls for configureUser", e);
+            }
+
+            setActiveAdmin(profileOwnerComponent, true, user.getIdentifier(), adminExtras);
+            setProfileOwner(profileOwnerPkg, ownerName, user.getIdentifier());
+            return user;
+        } finally {
+            restoreCallingIdentity(id);
+        }
+    }
+
+    @Override
     public boolean removeUser(ComponentName who, UserHandle userHandle) {
         synchronized (this) {
             if (who == null) {
@@ -3514,111 +3685,6 @@
     }
 
     @Override
-    public void enableSystemApp(ComponentName who, String packageName) {
-        synchronized (this) {
-            if (who == null) {
-                throw new NullPointerException("ComponentName is null");
-            }
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-
-            int userId = UserHandle.getCallingUserId();
-            long id = Binder.clearCallingIdentity();
-
-            try {
-                UserManager um = UserManager.get(mContext);
-                if (!um.getUserInfo(userId).isManagedProfile()) {
-                    throw new IllegalStateException(
-                            "Only call this method from a managed profile.");
-                }
-
-                // TODO: Use UserManager::getProfileParent when available.
-                UserInfo primaryUser = um.getUserInfo(UserHandle.USER_OWNER);
-
-                if (DBG) {
-                    Slog.v(LOG_TAG, "installing " + packageName + " for "
-                            + userId);
-                }
-
-                IPackageManager pm = AppGlobals.getPackageManager();
-                if (!isSystemApp(pm, packageName, primaryUser.id)) {
-                    throw new IllegalArgumentException("Only system apps can be enabled this way.");
-                }
-
-                // Install the app.
-                pm.installExistingPackageAsUser(packageName, userId);
-
-            } catch (RemoteException re) {
-                // shouldn't happen
-                Slog.wtf(LOG_TAG, "Failed to install " + packageName, re);
-            } finally {
-                restoreCallingIdentity(id);
-            }
-        }
-    }
-
-    @Override
-    public int enableSystemAppWithIntent(ComponentName who, Intent intent) {
-        synchronized (this) {
-            if (who == null) {
-                throw new NullPointerException("ComponentName is null");
-            }
-
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-
-            int userId = UserHandle.getCallingUserId();
-            long id = Binder.clearCallingIdentity();
-
-            try {
-                UserManager um = UserManager.get(mContext);
-                if (!um.getUserInfo(userId).isManagedProfile()) {
-                    throw new IllegalStateException(
-                            "Only call this method from a managed profile.");
-                }
-
-                // TODO: Use UserManager::getProfileParent when available.
-                UserInfo primaryUser = um.getUserInfo(UserHandle.USER_OWNER);
-
-                IPackageManager pm = AppGlobals.getPackageManager();
-                List<ResolveInfo> activitiesToEnable = pm.queryIntentActivities(intent,
-                        intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                        0, // no flags
-                        primaryUser.id);
-
-                if (DBG) Slog.d(LOG_TAG, "Enabling system activities: " + activitiesToEnable);
-                int numberOfAppsInstalled = 0;
-                if (activitiesToEnable != null) {
-                    for (ResolveInfo info : activitiesToEnable) {
-                        if (info.activityInfo != null) {
-
-                            if (!isSystemApp(pm, info.activityInfo.packageName, primaryUser.id)) {
-                                throw new IllegalArgumentException(
-                                        "Only system apps can be enabled this way.");
-                            }
-
-
-                            numberOfAppsInstalled++;
-                            pm.installExistingPackageAsUser(info.activityInfo.packageName, userId);
-                        }
-                    }
-                }
-                return numberOfAppsInstalled;
-            } catch (RemoteException e) {
-                // shouldn't happen
-                Slog.wtf(LOG_TAG, "Failed to resolve intent for: " + intent);
-                return 0;
-            } finally {
-                restoreCallingIdentity(id);
-            }
-        }
-    }
-
-    private boolean isSystemApp(IPackageManager pm, String packageName, int userId)
-            throws RemoteException {
-        ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0, userId);
-        return (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) > 0;
-    }
-
-    @Override
     public void setAccountManagementDisabled(ComponentName who, String accountType,
             boolean disabled) {
         if (!mHasFeature) {
@@ -3764,4 +3830,40 @@
             }
         }
     }
+
+    @Override
+    public void setMasterVolumeMuted(ComponentName who, boolean on) {
+        final ContentResolver contentResolver = mContext.getContentResolver();
+
+        synchronized (this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+            IAudioService iAudioService = IAudioService.Stub.asInterface(
+                    ServiceManager.getService(Context.AUDIO_SERVICE));
+            try{
+                iAudioService.setMasterMute(on, 0, who.getPackageName(), null);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Failed to setMasterMute", re);
+            }
+        }
+    }
+
+    @Override
+    public boolean isMasterVolumeMuted(ComponentName who) {
+        final ContentResolver contentResolver = mContext.getContentResolver();
+
+        synchronized (this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+            AudioManager audioManager =
+                    (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+            return audioManager.isMasterMute();
+        }
+    }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 54be537..3102cce 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -81,6 +81,7 @@
 import com.android.server.pm.UserManagerService;
 import com.android.server.power.PowerManagerService;
 import com.android.server.power.ShutdownThread;
+import com.android.server.restrictions.RestrictionsManagerService;
 import com.android.server.search.SearchManagerService;
 import com.android.server.statusbar.StatusBarManagerService;
 import com.android.server.storage.DeviceStorageMonitorService;
@@ -126,7 +127,7 @@
     private static final String WIFI_SERVICE_CLASS =
             "com.android.server.wifi.WifiService";
     private static final String WIFI_PASSPOINT_SERVICE_CLASS =
-            "com.android.server.wifi.passpoint.PasspointService";
+            "com.android.server.wifi.passpoint.WifiPasspointService";
     private static final String WIFI_P2P_SERVICE_CLASS =
             "com.android.server.wifi.p2p.WifiP2pService";
     private static final String ETHERNET_SERVICE_CLASS =
@@ -328,7 +329,6 @@
         IPackageManager pm = null;
         WindowManagerService wm = null;
         BluetoothManagerService bluetooth = null;
-        DockObserver dock = null;
         UsbService usb = null;
         SerialService serial = null;
         RecognitionManagerService recognition = null;
@@ -643,18 +643,18 @@
                 }
 
                 try {
-                    mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
-                } catch (Throwable e) {
-                    reportWtf("starting Wi-Fi Service", e);
-                }
-
-                try {
                     mSystemServiceManager.startService(WIFI_PASSPOINT_SERVICE_CLASS);
                 } catch (Throwable e) {
                     reportWtf("starting Wi-Fi PasspointService", e);
                 }
 
                 try {
+                    mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
+                } catch (Throwable e) {
+                    reportWtf("starting Wi-Fi Service", e);
+                }
+
+                try {
                     Slog.i(TAG, "Wi-Fi Scanning Service");
                     mSystemServiceManager.startService(
                             "com.android.server.wifi.WifiScanningService");
@@ -795,13 +795,7 @@
             }
 
             if (!disableNonCoreServices) {
-                try {
-                    Slog.i(TAG, "Dock Observer");
-                    // Listen for dock station changes
-                    dock = new DockObserver(context);
-                } catch (Throwable e) {
-                    reportWtf("starting DockObserver", e);
-                }
+                mSystemServiceManager.startService(DockObserver.class);
             }
 
             if (!disableMedia) {
@@ -954,6 +948,12 @@
             }
 
             try {
+                mSystemServiceManager.startService(RestrictionsManagerService.class);
+            } catch (Throwable e) {
+                reportWtf("starting RestrictionsManagerService", e);
+            }
+
+            try {
                 mSystemServiceManager.startService(MediaSessionService.class);
             } catch (Throwable e) {
                 reportWtf("starting MediaSessionService", e);
@@ -998,8 +998,7 @@
 
             try {
                 Slog.i(TAG, "LauncherAppsService");
-                LauncherAppsService las = new LauncherAppsService(context);
-                ServiceManager.addService(Context.LAUNCHER_APPS_SERVICE, las);
+                mSystemServiceManager.startService(LauncherAppsService.class);
             } catch (Throwable t) {
                 reportWtf("starting LauncherAppsService", t);
             }
@@ -1085,7 +1084,6 @@
         final NetworkPolicyManagerService networkPolicyF = networkPolicy;
         final ConnectivityService connectivityF = connectivity;
         final NetworkScoreService networkScoreF = networkScore;
-        final DockObserver dockF = dock;
         final WallpaperManagerService wallpaperF = wallpaper;
         final InputMethodManagerService immF = imm;
         final RecognitionManagerService recognitionF = recognition;
@@ -1159,11 +1157,6 @@
                     reportWtf("making Connectivity Service ready", e);
                 }
                 try {
-                    if (dockF != null) dockF.systemReady();
-                } catch (Throwable e) {
-                    reportWtf("making Dock Service ready", e);
-                }
-                try {
                     if (recognitionF != null) recognitionF.systemReady();
                 } catch (Throwable e) {
                     reportWtf("making Recognition Service ready", e);
diff --git a/services/print/java/com/android/server/print/RemotePrintService.java b/services/print/java/com/android/server/print/RemotePrintService.java
index 1bb61d2..a8c739c 100644
--- a/services/print/java/com/android/server/print/RemotePrintService.java
+++ b/services/print/java/com/android/server/print/RemotePrintService.java
@@ -121,15 +121,7 @@
         throwIfDestroyed();
 
         // Stop tracking printers.
-        if (mTrackedPrinterList != null) {
-            final int trackedPrinterCount = mTrackedPrinterList.size();
-            for (int i = 0; i < trackedPrinterCount; i++) {
-                PrinterId printerId = mTrackedPrinterList.get(i);
-                if (printerId.getServiceName().equals(mComponentName)) {
-                    handleStopPrinterStateTracking(printerId);
-                }
-            }
-        }
+        stopTrackingAllPrinters();
 
         // Stop printer discovery.
         if (mDiscoveryPriorityList != null) {
@@ -270,7 +262,7 @@
             try {
                 mPrintService.createPrinterDiscoverySession();
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error creating printer dicovery session.", re);
+                Slog.e(LOG_TAG, "Error creating printer discovery session.", re);
             }
         }
     }
@@ -365,10 +357,14 @@
             if (DEBUG) {
                 Slog.i(LOG_TAG, "[user: " + mUserId + "] stopPrinterDiscovery()");
             }
+
+            // Stop tracking printers.
+            stopTrackingAllPrinters();
+
             try {
                 mPrintService.stopPrinterDiscovery();
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error stopping printer dicovery.", re);
+                Slog.e(LOG_TAG, "Error stopping printer discovery.", re);
             }
         }
     }
@@ -466,6 +462,19 @@
         }
     }
 
+    private void stopTrackingAllPrinters() {
+        if (mTrackedPrinterList == null) {
+            return;
+        }
+        final int trackedPrinterCount = mTrackedPrinterList.size();
+        for (int i = trackedPrinterCount - 1; i >= 0; i--) {
+            PrinterId printerId = mTrackedPrinterList.get(i);
+            if (printerId.getServiceName().equals(mComponentName)) {
+                handleStopPrinterStateTracking(printerId);
+            }
+        }
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         String tab = "  ";
         pw.append(prefix).append("service:").println();
diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java
index ffe9806..9496cae 100644
--- a/services/print/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java
@@ -44,7 +44,7 @@
 
 /**
  * This represents the remote print spooler as a local object to the
- * PrintManagerSerivce. It is responsible to connecting to the remote
+ * PrintManagerService. It is responsible to connecting to the remote
  * spooler if needed, to make the timed remote calls, to handle
  * remote exceptions, and to bind/unbind to the remote instance as
  * needed.
@@ -99,7 +99,7 @@
         mClient = new PrintSpoolerClient(this);
         mIntent = new Intent();
         mIntent.setComponent(new ComponentName("com.android.printspooler",
-                "com.android.printspooler.PrintSpoolerService"));
+                "com.android.printspooler.model.PrintSpoolerService"));
     }
 
     public final List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, int state,
diff --git a/services/restrictions/Android.mk b/services/restrictions/Android.mk
new file mode 100644
index 0000000..fcf8626
--- /dev/null
+++ b/services/restrictions/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := services.restrictions
+
+LOCAL_SRC_FILES += \
+      $(call all-java-files-under,java)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
new file mode 100644
index 0000000..e1f77b3
--- /dev/null
+++ b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.restrictions;
+
+import android.Manifest;
+import android.app.AppGlobals;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.admin.IDevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IRestrictionsManager;
+import android.content.RestrictionsManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IUserManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.SystemService;
+
+/**
+ * SystemService wrapper for the RestrictionsManager implementation. Publishes the
+ * Context.RESTRICTIONS_SERVICE.
+ */
+
+public final class RestrictionsManagerService extends SystemService {
+    private final RestrictionsManagerImpl mRestrictionsManagerImpl;
+
+    public RestrictionsManagerService(Context context) {
+        super(context);
+        mRestrictionsManagerImpl = new RestrictionsManagerImpl(context);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.RESTRICTIONS_SERVICE, mRestrictionsManagerImpl);
+    }
+
+    class RestrictionsManagerImpl extends IRestrictionsManager.Stub {
+        private final Context mContext;
+        private final IUserManager mUm;
+        private final IDevicePolicyManager mDpm;
+
+        public RestrictionsManagerImpl(Context context) {
+            mContext = context;
+            mUm = (IUserManager) getBinderService(Context.USER_SERVICE);
+            mDpm = (IDevicePolicyManager) getBinderService(Context.DEVICE_POLICY_SERVICE);
+        }
+
+        @Override
+        public Bundle getApplicationRestrictions(String packageName) throws RemoteException {
+            return mUm.getApplicationRestrictions(packageName);
+        }
+
+        @Override
+        public boolean hasRestrictionsProvider() throws RemoteException {
+            int userHandle = UserHandle.getCallingUserId();
+            if (mDpm != null) {
+                long ident = Binder.clearCallingIdentity();
+                try {
+                    return mDpm.getRestrictionsProvider(userHandle) != null;
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public void requestPermission(String packageName, String requestTemplate,
+                Bundle requestData) throws RemoteException {
+            int callingUid = Binder.getCallingUid();
+            int userHandle = UserHandle.getUserId(callingUid);
+            if (mDpm != null) {
+                long ident = Binder.clearCallingIdentity();
+                try {
+                    ComponentName restrictionsProvider =
+                            mDpm.getRestrictionsProvider(userHandle);
+                    // Check if there is a restrictions provider
+                    if (restrictionsProvider == null) {
+                        throw new IllegalStateException(
+                            "Cannot request permission without a restrictions provider registered");
+                    }
+                    // Check that the packageName matches the caller.
+                    enforceCallerMatchesPackage(callingUid, packageName, "Package name does not" +
+                            " match caller ");
+                    // Prepare and broadcast the intent to the provider
+                    Intent intent = new Intent(RestrictionsManager.ACTION_REQUEST_PERMISSION);
+                    intent.setComponent(restrictionsProvider);
+                    intent.putExtra(RestrictionsManager.EXTRA_PACKAGE_NAME, packageName);
+                    intent.putExtra(RestrictionsManager.EXTRA_TEMPLATE_ID, requestTemplate);
+                    intent.putExtra(RestrictionsManager.EXTRA_REQUEST_BUNDLE, requestData);
+                    mContext.sendBroadcastAsUser(intent, new UserHandle(userHandle));
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        }
+
+        private void enforceCallerMatchesPackage(int callingUid, String packageName,
+                String message) {
+            try {
+                String[] pkgs = AppGlobals.getPackageManager().getPackagesForUid(callingUid);
+                if (pkgs != null) {
+                    if (!ArrayUtils.contains(pkgs, packageName)) {
+                        throw new SecurityException(message + callingUid);
+                    }
+                }
+            } catch (RemoteException re) {
+                // Shouldn't happen
+            }
+        }
+
+        @Override
+        public void notifyPermissionResponse(String packageName, Bundle response)
+                throws RemoteException {
+            // Check caller
+            int callingUid = Binder.getCallingUid();
+            int userHandle = UserHandle.getUserId(callingUid);
+            if (mDpm != null) {
+                long ident = Binder.clearCallingIdentity();
+                try {
+                    ComponentName permProvider = mDpm.getRestrictionsProvider(userHandle);
+                    if (permProvider == null) {
+                        throw new SecurityException("No restrictions provider registered for user");
+                    }
+                    enforceCallerMatchesPackage(callingUid, permProvider.getPackageName(),
+                            "Restrictions provider does not match caller ");
+
+                    // Post the response to target package
+                    Intent responseIntent = new Intent(
+                            RestrictionsManager.ACTION_PERMISSION_RESPONSE_RECEIVED);
+                    responseIntent.setPackage(packageName);
+                    responseIntent.putExtra(RestrictionsManager.EXTRA_RESPONSE_BUNDLE, response);
+                    mContext.sendBroadcastAsUser(responseIntent, new UserHandle(userHandle));
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        }
+    }
+}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 7848b1d..636dd4d 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -35,7 +35,8 @@
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
-    
+    <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
+
     <application>
         <uses-library android:name="android.test.runner" />
 
@@ -53,6 +54,15 @@
           </intent-filter>
         </service>
 
+        <receiver android:name="com.android.server.devicepolicy.ApplicationRestrictionsTest$AdminReceiver"
+                android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data android:name="android.app.device_admin"
+                       android:resource="@xml/device_admin_sample" />
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+            </intent-filter>
+        </receiver>
+
     </application>
 
     <instrumentation
diff --git a/services/tests/servicestests/res/xml/device_admin_sample.xml b/services/tests/servicestests/res/xml/device_admin_sample.xml
new file mode 100644
index 0000000..032debb
--- /dev/null
+++ b/services/tests/servicestests/res/xml/device_admin_sample.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-policies>
+        <limit-password />
+        <watch-login />
+        <reset-password />
+        <force-lock />
+        <wipe-data />
+        <expire-password />
+        <encrypted-storage />
+        <disable-camera />
+        <disable-keyguard-features />
+    </uses-policies>
+</device-admin>
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java
new file mode 100644
index 0000000..8e8e4e6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Tests for application restrictions persisting via profile owner:
+ *   make -j FrameworksServicesTests
+ *   runtest --path frameworks/base/services/tests/servicestests/ \
+ *       src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java
+ */
+public class ApplicationRestrictionsTest extends AndroidTestCase {
+
+    static DevicePolicyManager sDpm;
+    static ComponentName sAdminReceiver;
+    private static final String RESTRICTED_APP = "com.example.restrictedApp";
+    static boolean sAddBack = false;
+
+    public static class AdminReceiver extends DeviceAdminReceiver {
+
+        @Override
+        public void onDisabled(Context context, Intent intent) {
+            if (sAddBack) {
+                sDpm.setActiveAdmin(sAdminReceiver, false);
+                sAddBack = false;
+            }
+
+            super.onDisabled(context, intent);
+        }
+    }
+
+    @Override
+    public void setUp() {
+        final Context context = getContext();
+        sAdminReceiver = new ComponentName(mContext.getPackageName(),
+                AdminReceiver.class.getName());
+        sDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        Settings.Secure.putInt(context.getContentResolver(),
+                Settings.Secure.USER_SETUP_COMPLETE, 0);
+        sDpm.setProfileOwner(context.getPackageName(), "Test", UserHandle.myUserId());
+        Settings.Secure.putInt(context.getContentResolver(),
+                Settings.Secure.USER_SETUP_COMPLETE, 1);
+        // Remove the admin if already registered. It's async, so add it back
+        // when the admin gets a broadcast. Otherwise add it back right away.
+        if (sDpm.isAdminActive(sAdminReceiver)) {
+            sAddBack = true;
+            sDpm.removeActiveAdmin(sAdminReceiver);
+        } else {
+            sDpm.setActiveAdmin(sAdminReceiver, false);
+        }
+    }
+
+    @Override
+    public void tearDown() {
+        Settings.Secure.putInt(getContext().getContentResolver(),
+                Settings.Secure.USER_SETUP_COMPLETE, 0);
+        sDpm.removeActiveAdmin(sAdminReceiver);
+        Settings.Secure.putInt(getContext().getContentResolver(),
+                Settings.Secure.USER_SETUP_COMPLETE, 1);
+    }
+
+    public void testSettingRestrictions() {
+        Bundle restrictions = new Bundle();
+        restrictions.putString("KEY_STRING", "Foo");
+        assertNotNull(sDpm.getApplicationRestrictions(sAdminReceiver, RESTRICTED_APP));
+        sDpm.setApplicationRestrictions(sAdminReceiver, RESTRICTED_APP, restrictions);
+        Bundle returned = sDpm.getApplicationRestrictions(sAdminReceiver, RESTRICTED_APP);
+        assertNotNull(returned);
+        assertEquals(returned.size(), 1);
+        assertEquals(returned.get("KEY_STRING"), "Foo");
+        sDpm.setApplicationRestrictions(sAdminReceiver, RESTRICTED_APP, new Bundle());
+        returned = sDpm.getApplicationRestrictions(sAdminReceiver, RESTRICTED_APP);
+        assertEquals(returned.size(), 0);
+    }
+
+    public void testRestrictionTypes() {
+        Bundle restrictions = new Bundle();
+        restrictions.putString("KEY_STRING", "Foo");
+        restrictions.putInt("KEY_INT", 7);
+        restrictions.putBoolean("KEY_BOOLEAN", true);
+        restrictions.putBoolean("KEY_BOOLEAN_2", false);
+        restrictions.putString("KEY_STRING_2", "Bar");
+        restrictions.putStringArray("KEY_STR_ARRAY", new String[] { "Foo", "Bar" });
+        sDpm.setApplicationRestrictions(sAdminReceiver, RESTRICTED_APP, restrictions);
+        Bundle returned = sDpm.getApplicationRestrictions(sAdminReceiver, RESTRICTED_APP);
+        assertTrue(returned.getBoolean("KEY_BOOLEAN"));
+        assertFalse(returned.getBoolean("KEY_BOOLEAN_2"));
+        assertFalse(returned.getBoolean("KEY_BOOLEAN_3"));
+        assertEquals(returned.getInt("KEY_INT"), 7);
+        assertTrue(returned.get("KEY_BOOLEAN") instanceof Boolean);
+        assertTrue(returned.get("KEY_INT") instanceof Integer);
+        assertEquals(returned.get("KEY_STRING"), "Foo");
+        assertEquals(returned.get("KEY_STRING_2"), "Bar");
+        assertTrue(returned.getStringArray("KEY_STR_ARRAY") instanceof String[]);
+        sDpm.setApplicationRestrictions(sAdminReceiver, RESTRICTED_APP, new Bundle());
+    }
+
+    public void testTextEscaping() {
+        String fancyText = "<This contains XML/> <JSON> "
+                + "{ \"One\": { \"OneOne\": \"11\", \"OneTwo\": \"12\" }, \"Two\": \"2\" } <JSON/>";
+        Bundle restrictions = new Bundle();
+        restrictions.putString("KEY_FANCY_TEXT", fancyText);
+        sDpm.setApplicationRestrictions(sAdminReceiver, RESTRICTED_APP, restrictions);
+        Bundle returned = sDpm.getApplicationRestrictions(sAdminReceiver, RESTRICTED_APP);
+        assertEquals(returned.getString("KEY_FANCY_TEXT"), fancyText);
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java b/services/tests/servicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
new file mode 100644
index 0000000..a6fdee9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.notification;
+
+import android.app.Notification;
+import android.os.Bundle;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.SpannableString;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class ValidateNotificationPeopleTest extends AndroidTestCase {
+
+    @SmallTest
+    public void testNoExtra() throws Exception {
+        Bundle bundle = new Bundle();
+        String[] result = ValidateNotificationPeople.getExtraPeople(bundle);
+        assertNull("lack of extra should return null", result);
+    }
+
+    @SmallTest
+    public void testSingleString() throws Exception {
+        String[] expected = { "foobar" };
+        Bundle bundle = new Bundle();
+        bundle.putString(Notification.EXTRA_PEOPLE, expected[0]);
+        String[] result = ValidateNotificationPeople.getExtraPeople(bundle);
+        assertStringArrayEquals("string should be in result[0]", expected, result);
+    }
+
+    @SmallTest
+    public void testSingleCharArray() throws Exception {
+        String[] expected = { "foobar" };
+        Bundle bundle = new Bundle();
+        bundle.putCharArray(Notification.EXTRA_PEOPLE, expected[0].toCharArray());
+        String[] result = ValidateNotificationPeople.getExtraPeople(bundle);
+        assertStringArrayEquals("char[] should be in result[0]", expected, result);
+    }
+
+    @SmallTest
+    public void testSingleCharSequence() throws Exception {
+        String[] expected = { "foobar" };
+        Bundle bundle = new Bundle();
+        bundle.putCharSequence(Notification.EXTRA_PEOPLE, new SpannableString(expected[0]));
+        String[] result = ValidateNotificationPeople.getExtraPeople(bundle);
+        assertStringArrayEquals("charSequence should be in result[0]", expected, result);
+    }
+
+    @SmallTest
+    public void testStringArraySingle() throws Exception {
+        Bundle bundle = new Bundle();
+        String[] expected = { "foobar" };
+        bundle.putStringArray(Notification.EXTRA_PEOPLE, expected);
+        String[] result = ValidateNotificationPeople.getExtraPeople(bundle);
+        assertStringArrayEquals("wrapped string should be in result[0]", expected, result);
+    }
+
+    @SmallTest
+    public void testStringArrayMultiple() throws Exception {
+        Bundle bundle = new Bundle();
+        String[] expected = { "foo", "bar", "baz" };
+        bundle.putStringArray(Notification.EXTRA_PEOPLE, expected);
+        String[] result = ValidateNotificationPeople.getExtraPeople(bundle);
+        assertStringArrayEquals("testStringArrayMultiple", expected, result);
+    }
+
+    @SmallTest
+    public void testStringArrayNulls() throws Exception {
+        Bundle bundle = new Bundle();
+        String[] expected = { "foo", null, "baz" };
+        bundle.putStringArray(Notification.EXTRA_PEOPLE, expected);
+        String[] result = ValidateNotificationPeople.getExtraPeople(bundle);
+        assertStringArrayEquals("testStringArrayNulls", expected, result);
+    }
+
+    @SmallTest
+    public void testCharSequenceArrayMultiple() throws Exception {
+        Bundle bundle = new Bundle();
+        String[] expected = { "foo", "bar", "baz" };
+        CharSequence[] charSeqArray = new CharSequence[expected.length];
+        for (int i = 0; i < expected.length; i++) {
+            charSeqArray[i] = new SpannableString(expected[i]);
+        }
+        bundle.putCharSequenceArray(Notification.EXTRA_PEOPLE, charSeqArray);
+        String[] result = ValidateNotificationPeople.getExtraPeople(bundle);
+        assertStringArrayEquals("testCharSequenceArrayMultiple", expected, result);
+    }
+
+    @SmallTest
+    public void testMixedCharSequenceArrayList() throws Exception {
+        Bundle bundle = new Bundle();
+        String[] expected = { "foo", "bar", "baz" };
+        CharSequence[] charSeqArray = new CharSequence[expected.length];
+        for (int i = 0; i < expected.length; i++) {
+            if (i % 2 == 0) {
+                charSeqArray[i] = expected[i];
+            } else {
+                charSeqArray[i] = new SpannableString(expected[i]);
+            }
+        }
+        bundle.putCharSequenceArray(Notification.EXTRA_PEOPLE, charSeqArray);
+        String[] result = ValidateNotificationPeople.getExtraPeople(bundle);
+        assertStringArrayEquals("testMixedCharSequenceArrayList", expected, result);
+    }
+
+    @SmallTest
+    public void testStringArrayList() throws Exception {
+        Bundle bundle = new Bundle();
+        String[] expected = { "foo", null, "baz" };
+        final ArrayList<String> stringArrayList = new ArrayList<String>(expected.length);
+        for (int i = 0; i < expected.length; i++) {
+            stringArrayList.add(expected[i]);
+        }
+        bundle.putStringArrayList(Notification.EXTRA_PEOPLE, stringArrayList);
+        String[] result = ValidateNotificationPeople.getExtraPeople(bundle);
+        assertStringArrayEquals("testStringArrayList", expected, result);
+    }
+
+    @SmallTest
+    public void testCharSequenceArrayList() throws Exception {
+        Bundle bundle = new Bundle();
+        String[] expected = { "foo", "bar", "baz" };
+        final ArrayList<CharSequence> stringArrayList =
+                new ArrayList<CharSequence>(expected.length);
+        for (int i = 0; i < expected.length; i++) {
+            stringArrayList.add(new SpannableString(expected[i]));
+        }
+        bundle.putCharSequenceArrayList(Notification.EXTRA_PEOPLE, stringArrayList);
+        String[] result = ValidateNotificationPeople.getExtraPeople(bundle);
+        assertStringArrayEquals("testCharSequenceArrayList", expected, result);
+    }
+
+    private void assertStringArrayEquals(String message, String[] expected, String[] result) {
+        String expectedString = Arrays.toString(expected);
+        String resultString = Arrays.toString(result);
+        assertEquals(message + ": arrays differ", expectedString, resultString);
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/UsbDebuggingManager.java b/services/usb/java/com/android/server/usb/UsbDebuggingManager.java
index 0946c5a..cc5d004 100644
--- a/services/usb/java/com/android/server/usb/UsbDebuggingManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDebuggingManager.java
@@ -84,7 +84,6 @@
             while (true) {
                 int count = inputStream.read(buffer);
                 if (count < 0) {
-                    Slog.e(TAG, "got " + count + " reading");
                     break;
                 }
 
@@ -100,9 +99,6 @@
                     break;
                 }
             }
-        } catch (IOException ex) {
-            Slog.e(TAG, "Communication error: ", ex);
-            throw ex;
         } finally {
             closeSocket();
         }
diff --git a/telecomm/java/android/telecomm/CallCapabilities.java b/telecomm/java/android/telecomm/CallCapabilities.java
index 5aff19c..b2b33a3 100644
--- a/telecomm/java/android/telecomm/CallCapabilities.java
+++ b/telecomm/java/android/telecomm/CallCapabilities.java
@@ -17,7 +17,7 @@
 package android.telecomm;
 
 /** Defines actions a call currently supports. */
-public final class CallCapabilities {
+public class CallCapabilities {
     /** Call can currently be put on hold or unheld. */
     public static final int HOLD               = 0x00000001;
 
@@ -27,60 +27,24 @@
     /** Call can currently be merged. */
     public static final int MERGE_CALLS        = 0x00000004;
 
-    /** Call can currently be swapped with another call. */
+     /* Call can currently be swapped with another call. */
     public static final int SWAP_CALLS         = 0x00000008;
 
-    /** Call currently supports adding another call to this one. */
+     /* Call currently supports adding another call to this one. */
     public static final int ADD_CALL           = 0x00000010;
 
-    /** Call supports responding via text option. */
+     /* Call supports responding via text option. */
     public static final int RESPOND_VIA_TEXT   = 0x00000020;
 
-    /** Call can be muted. */
+     /* Call can be muted. */
     public static final int MUTE               = 0x00000040;
 
-    /** Call supports generic conference mode. */
+     /* Call supports generic conference mode. */
     public static final int GENERIC_CONFERENCE = 0x00000080;
 
-    /** Call currently supports switch between connections. */
+     /* Call currently supports switch between connections. */
     public static final int CONNECTION_HANDOFF = 0x00000100;
 
     public static final int ALL = HOLD | SUPPORT_HOLD | MERGE_CALLS | SWAP_CALLS | ADD_CALL
             | RESPOND_VIA_TEXT | MUTE | GENERIC_CONFERENCE | CONNECTION_HANDOFF;
-
-    public static String toString(int capabilities) {
-        StringBuilder builder = new StringBuilder();
-        builder.append("[Capabilities:");
-        if ((capabilities & HOLD) != 0) {
-            builder.append(" HOLD");
-        }
-        if ((capabilities & SUPPORT_HOLD) != 0) {
-            builder.append(" SUPPORT_HOLD");
-        }
-        if ((capabilities & MERGE_CALLS) != 0) {
-            builder.append(" MERGE_CALLS");
-        }
-        if ((capabilities & SWAP_CALLS) != 0) {
-            builder.append(" SWAP_CALLS");
-        }
-        if ((capabilities & ADD_CALL) != 0) {
-            builder.append(" ADD_CALL");
-        }
-        if ((capabilities & RESPOND_VIA_TEXT) != 0) {
-            builder.append(" RESPOND_VIA_TEXT");
-        }
-        if ((capabilities & MUTE) != 0) {
-            builder.append(" MUTE");
-        }
-        if ((capabilities & GENERIC_CONFERENCE) != 0) {
-            builder.append(" GENERIC_CONFERENCE");
-        }
-        if ((capabilities & CONNECTION_HANDOFF) != 0) {
-            builder.append(" HANDOFF");
-        }
-        builder.append("]");
-        return builder.toString();
-    }
-
-    private CallCapabilities() {}
 }
diff --git a/telecomm/java/android/telecomm/CallService.java b/telecomm/java/android/telecomm/CallService.java
index 0b5981c..a254459 100644
--- a/telecomm/java/android/telecomm/CallService.java
+++ b/telecomm/java/android/telecomm/CallService.java
@@ -61,7 +61,7 @@
     private static final int MSG_ON_AUDIO_STATE_CHANGED = 11;
     private static final int MSG_PLAY_DTMF_TONE = 12;
     private static final int MSG_STOP_DTMF_TONE = 13;
-    private static final int MSG_CONFERENCE = 14;
+    private static final int MSG_ADD_TO_CONFERENCE = 14;
     private static final int MSG_SPLIT_FROM_CONFERENCE = 15;
     private static final int MSG_ON_POST_DIAL_CONTINUE = 16;
 
@@ -128,12 +128,24 @@
                 case MSG_STOP_DTMF_TONE:
                     stopDtmfTone((String) msg.obj);
                     break;
-                case MSG_CONFERENCE: {
+                case MSG_ADD_TO_CONFERENCE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        @SuppressWarnings("unchecked")
+                        List<String> callIds = (List<String>) args.arg2;
+                        String conferenceCallId = (String) args.arg1;
+                        addToConference(conferenceCallId, callIds);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_SPLIT_FROM_CONFERENCE: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
                         String conferenceCallId = (String) args.arg1;
                         String callId = (String) args.arg2;
-                        conference(conferenceCallId, callId);
+                        splitFromConference(conferenceCallId, callId);
                     } finally {
                         args.recycle();
                     }
@@ -150,9 +162,6 @@
                     }
                     break;
                 }
-                case MSG_SPLIT_FROM_CONFERENCE:
-                    splitFromConference((String) msg.obj);
-                    break;
                 default:
                     break;
             }
@@ -236,16 +245,19 @@
         }
 
         @Override
-        public void conference(String conferenceCallId, String callId) {
+        public void addToConference(String conferenceCallId, List<String> callsToConference) {
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = conferenceCallId;
-            args.arg2 = callId;
-            mMessageHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget();
+            args.arg2 = callsToConference;
+            mMessageHandler.obtainMessage(MSG_ADD_TO_CONFERENCE, args).sendToTarget();
         }
 
         @Override
-        public void splitFromConference(String callId) {
-            mMessageHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, callId).sendToTarget();
+        public void splitFromConference(String conferenceCallId, String callId) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = conferenceCallId;
+            args.arg2 = callId;
+            mMessageHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, args).sendToTarget();
         }
 
         @Override
@@ -412,22 +424,24 @@
     public abstract void onAudioStateChanged(String activeCallId, CallAudioState audioState);
 
     /**
-     * Conferences the specified call.
+     * Adds the specified calls to the specified conference call.
      *
      * @param conferenceCallId The unique ID of the conference call onto which the specified calls
      *         should be added.
-     * @param callId The call to conference.
+     * @param callIds The calls to add to the conference call.
      * @hide
      */
-    public abstract void conference(String conferenceCallId, String callId);
+    public abstract void addToConference(String conferenceCallId, List<String> callIds);
 
     /**
-     * Removes the specified call from a conference call.
+     * Removes the specified call from the specified conference call. This is a no-op if the call
+     * is not already part of the conference call.
      *
+     * @param conferenceCallId The conference call.
      * @param callId The call to remove from the conference call
      * @hide
      */
-    public abstract void splitFromConference(String callId);
+    public abstract void splitFromConference(String conferenceCallId, String callId);
 
     public void onPostDialContinue(String callId, boolean proceed) {}
     public void onPostDialWait(Connection conn, String remaining) {}
diff --git a/telecomm/java/android/telecomm/CallServiceAdapter.java b/telecomm/java/android/telecomm/CallServiceAdapter.java
index ce89321..8c3ddad 100644
--- a/telecomm/java/android/telecomm/CallServiceAdapter.java
+++ b/telecomm/java/android/telecomm/CallServiceAdapter.java
@@ -179,12 +179,12 @@
      * Indicates that the specified call can conference with any of the specified list of calls.
      *
      * @param callId The unique ID of the call.
-     * @param canConference Specified whether or not the call can be conferenced.
+     * @param conferenceCapableCallIds The unique IDs of the calls which can be conferenced.
      * @hide
      */
-    public void setCanConference(String callId, boolean canConference) {
+    public void setCanConferenceWith(String callId, List<String> conferenceCapableCallIds) {
         try {
-            mAdapter.setCanConference(callId, canConference);
+            mAdapter.setCanConferenceWith(callId, conferenceCapableCallIds);
         } catch (RemoteException ignored) {
         }
     }
@@ -193,14 +193,13 @@
      * Indicates whether or not the specified call is currently conferenced into the specified
      * conference call.
      *
+     * @param conferenceCallId The unique ID of the conference call.
      * @param callId The unique ID of the call being conferenced.
-     * @param conferenceCallId The unique ID of the conference call. Null if call is not
-     *         conferenced.
      * @hide
      */
-    public void setIsConferenced(String callId, String conferenceCallId) {
+    public void setIsConferenced(String conferenceCallId, String callId, boolean isConferenced) {
         try {
-            mAdapter.setIsConferenced(callId, conferenceCallId);
+            mAdapter.setIsConferenced(conferenceCallId, callId, isConferenced);
         } catch (RemoteException ignored) {
         }
     }
@@ -227,14 +226,14 @@
     }
 
     /**
-     * Indicates that a new conference call has been created.
+     * Instructs Telecomm to handoff the call to another call service.
      *
-     * @param callId The unique ID of the conference call.
+     * @param callId The identifier of the call to handoff.
      */
-    public void addConferenceCall(String callId) {
+    public void handoffCall(String callId) {
         try {
-            mAdapter.addConferenceCall(callId, null);
-        } catch (RemoteException ignored) {
+            mAdapter.handoffCall(callId);
+        } catch (RemoteException e) {
         }
     }
 }
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index 164eeff..344814f 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -19,10 +19,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 
 /**
@@ -38,8 +35,6 @@
         void onDisconnected(Connection c, int cause, String message);
         void onRequestingRingback(Connection c, boolean ringback);
         void onDestroyed(Connection c);
-        void onConferenceCapableChanged(Connection c, boolean isConferenceCapable);
-        void onParentConnectionChanged(Connection c, Connection parent);
     }
 
     public static class ListenerBase implements Listener {
@@ -70,14 +65,6 @@
         /** {@inheritDoc} */
         @Override
         public void onRequestingRingback(Connection c, boolean ringback) {}
-
-        /** ${inheritDoc} */
-        @Override
-        public void onConferenceCapableChanged(Connection c, boolean isConferenceCapable) {}
-
-        /** ${inheritDoc} */
-        @Override
-        public void onParentConnectionChanged(Connection c, Connection parent) {}
     }
 
     public final class State {
@@ -92,14 +79,10 @@
     }
 
     private final Set<Listener> mListeners = new HashSet<>();
-    private final List<Connection> mChildConnections = new ArrayList<>();
-
     private int mState = State.NEW;
     private CallAudioState mCallAudioState;
     private Uri mHandle;
     private boolean mRequestingRingback = false;
-    private boolean mIsConferenceCapable = false;
-    private Connection mParentConnection;
 
     /**
      * Create a new Connection.
@@ -107,13 +90,6 @@
     protected Connection() {}
 
     /**
-     * The handle (e.g., phone number) to which this Connection is currently communicating.
-     *
-     * IMPORTANT: If an incoming connection has a phone number (or other handle) that the user
-     * is not supposed to be able to see (e.g. it is PRESENTATION_RESTRICTED), then a compliant
-     * ConnectionService implementation MUST NOT reveal this phone number and MUST return
-     * {@code null} from this method.
-     *
      * @return The handle (e.g., phone number) to which this Connection
      *         is currently communicating.
      */
@@ -200,16 +176,6 @@
     }
 
     /**
-     * Separates this Connection from a parent connection.
-     *
-     * @hide
-     */
-    public final void separate() {
-        Log.d(this, "separate");
-        onSeparate();
-    }
-
-    /**
      * Abort this Connection. The Connection will immediately transition to
      * the {@link State#DISCONNECTED} state, and send no notifications of this
      * or any other future events.
@@ -274,14 +240,6 @@
     }
 
     /**
-     * TODO(santoscordon): Needs updated documentation.
-     */
-    public final void conference() {
-        Log.d(this, "conference");
-        onConference();
-    }
-
-    /**
      * Inform this Connection that the state of its audio output has been changed externally.
      *
      * @param state The new audio state.
@@ -316,7 +274,7 @@
     }
 
     /**
-     * Returns whether this connection is requesting that the system play a ringback tone
+     * @return Whether this connection is requesting that the system play a ringback tone
      * on its behalf.
      */
     public boolean isRequestingRingback() {
@@ -324,38 +282,6 @@
     }
 
     /**
-     * Returns whether this connection is a conference connection (has child connections).
-     */
-    public boolean isConferenceConnection() {
-        return !mChildConnections.isEmpty();
-    }
-
-    public void setParentConnection(Connection parentConnection) {
-        Log.d(this, "parenting %s to %s", this, parentConnection);
-        if (mParentConnection != parentConnection) {
-            if (mParentConnection != null) {
-                mParentConnection.removeChild(this);
-            }
-            mParentConnection = parentConnection;
-            if (mParentConnection != null) {
-                mParentConnection.addChild(this);
-                // do something if the child connections goes down to ZERO.
-            }
-            for (Listener l : mListeners) {
-                l.onParentConnectionChanged(this, mParentConnection);
-            }
-        }
-    }
-
-    public Connection getParentConnection() {
-        return mParentConnection;
-    }
-
-    public List<Connection> getChildConnections() {
-        return mChildConnections;
-    }
-
-    /**
      * Sets the value of the {@link #getHandle()} property and notifies listeners.
      *
      * @param handle The new handle.
@@ -433,32 +359,6 @@
     }
 
     /**
-     * TODO(santoscordon): Needs documentation.
-     */
-    protected void setIsConferenceCapable(boolean isConferenceCapable) {
-        if (mIsConferenceCapable != isConferenceCapable) {
-            mIsConferenceCapable = isConferenceCapable;
-            for (Listener l : mListeners) {
-                l.onConferenceCapableChanged(this, mIsConferenceCapable);
-            }
-        }
-    }
-
-    /**
-     * TODO(santoscordon): Needs documentation.
-     */
-    protected void setDestroyed() {
-        // It is possible that onDestroy() will trigger the listener to remove itself which will
-        // result in a concurrent modification exception. To counteract this we make a copy of the
-        // listeners and iterate on that.
-        for (Listener l : new ArrayList<>(mListeners)) {
-            if (mListeners.contains(l)) {
-                l.onDestroyed(this);
-            }
-        }
-    }
-
-    /**
      * Notifies this Connection and listeners that the {@link #getCallAudioState()} property
      * has a new value.
      *
@@ -518,11 +418,6 @@
     protected void onDisconnect() {}
 
     /**
-     * Notifies this Connection of a request to disconnect.
-     */
-    protected void onSeparate() {}
-
-    /**
      * Notifies this Connection of a request to abort.
      */
     protected void onAbort() {}
@@ -554,28 +449,6 @@
      */
     protected void onPostDialContinue(boolean proceed) {}
 
-    /**
-     * TODO(santoscordon): Needs documentation.
-     */
-    protected void onConference() {}
-
-    /**
-     * TODO(santoscordon): Needs documentation.
-     */
-    protected void onChildrenChanged(List<Connection> children) {}
-
-    private void addChild(Connection connection) {
-        Log.d(this, "adding child %s", connection);
-        mChildConnections.add(connection);
-        onChildrenChanged(mChildConnections);
-    }
-
-    private void removeChild(Connection connection) {
-        Log.d(this, "removing child %s", connection);
-        mChildConnections.remove(connection);
-        onChildrenChanged(mChildConnections);
-    }
-
     private void setState(int state) {
         Log.d(this, "setState: %s", stateToString(state));
         onSetState(state);
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index d974509..59e977d 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -20,15 +20,10 @@
 import android.os.Bundle;
 import android.telephony.DisconnectCause;
 
-import android.os.SystemClock;
-
-import java.util.Collection;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * A {@link android.app.Service} that provides telephone connections to
@@ -37,6 +32,7 @@
 public abstract class ConnectionService extends CallService {
     // Flag controlling whether PII is emitted into the logs
     private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
+
     private static final Connection NULL_CONNECTION = new Connection() {};
 
     // Mappings from Connections to IDs as understood by the current CallService implementation
@@ -103,20 +99,6 @@
             Log.d(this, "Adapter onRingback %b", ringback);
             getAdapter().setRequestingRingback(id, ringback);
         }
-
-        @Override
-        public void onConferenceCapableChanged(Connection c, boolean isConferenceCapable) {
-            String id = mIdByConnection.get(c);
-            getAdapter().setCanConference(id, isConferenceCapable);
-        }
-
-        /** ${inheritDoc} */
-        @Override
-        public void onParentConnectionChanged(Connection c, Connection parent) {
-            String id = mIdByConnection.get(c);
-            String parentId = parent == null ? null : mIdByConnection.get(parent);
-            getAdapter().setIsConferenced(id, parentId);
-        }
     };
 
     @Override
@@ -128,7 +110,8 @@
                     @Override
                     public void onResult(Uri handle, Subscription... result) {
                         boolean isCompatible = result.length > 0;
-                        Log.d(this, "adapter setIsCompatibleWith ");
+                        Log.d(this, "adapter setIsCompatibleWith "
+                                + callInfo.getId() + " " + isCompatible);
                         getAdapter().setIsCompatibleWith(callInfo.getId(), isCompatible);
                     }
 
@@ -152,7 +135,7 @@
                 new Response<ConnectionRequest, Connection>() {
                     @Override
                     public void onResult(ConnectionRequest request, Connection... result) {
-                        if (result != null && result.length != 1) {
+                        if (result.length != 1) {
                             Log.d(this, "adapter handleFailedOutgoingCall %s", callInfo);
                             getAdapter().handleFailedOutgoingCall(
                                     request,
@@ -162,10 +145,10 @@
                                 c.abort();
                             }
                         } else {
+                            addConnection(callInfo.getId(), result[0]);
                             Log.d(this, "adapter handleSuccessfulOutgoingCall %s",
                                     callInfo.getId());
                             getAdapter().handleSuccessfulOutgoingCall(callInfo.getId());
-                            addConnection(callInfo.getId(), result[0]);
                         }
                     }
 
@@ -194,7 +177,7 @@
                 new Response<ConnectionRequest, Connection>() {
                     @Override
                     public void onResult(ConnectionRequest request, Connection... result) {
-                        if (result != null && result.length != 1) {
+                        if (result.length != 1) {
                             Log.d(this, "adapter handleFailedOutgoingCall %s", callId);
                             getAdapter().handleFailedOutgoingCall(
                                     request,
@@ -275,43 +258,27 @@
 
     /** @hide */
     @Override
-    public final void conference(final String conferenceCallId, String callId) {
-        Log.d(this, "conference %s, %s", conferenceCallId, callId);
+    public final void addToConference(String conferenceCallId, List<String> callIds) {
+        Log.d(this, "addToConference %s, %s", conferenceCallId, callIds);
 
-        Connection connection = findConnectionForAction(callId, "conference");
-        if (connection == NULL_CONNECTION) {
-            Log.w(this, "Connection missing in conference request %s.", callId);
-            return;
+        List<Connection> connections = new LinkedList<>();
+        for (String id : callIds) {
+            Connection connection = findConnectionForAction(id, "addToConference");
+            if (connection == NULL_CONNECTION) {
+                Log.w(this, "Connection missing in conference request %s.", id);
+                return;
+            }
+            connections.add(connection);
         }
 
-        onCreateConferenceConnection(conferenceCallId, connection,
-                new Response<String, Connection>() {
-                    /** ${inheritDoc} */
-                    @Override
-                    public void onResult(String ignored, Connection... result) {
-                        Log.d(this, "onCreateConference.Response %s", (Object[]) result);
-                        if (result != null && result.length == 1) {
-                            Connection conferenceConnection = result[0];
-                            if (!mIdByConnection.containsKey(conferenceConnection)) {
-                                Log.v(this, "sending new conference call %s", conferenceCallId);
-                                getAdapter().addConferenceCall(conferenceCallId);
-                                addConnection(conferenceCallId, conferenceConnection);
-                            }
-                        }
-                    }
-
-                    /** ${inheritDoc} */
-                    @Override
-                    public void onError(String request, int code, String reason) {
-                        // no-op
-                    }
-                });
+        // TODO(santoscordon): Find an existing conference call or create a new one. Then call
+        // conferenceWith on it.
     }
 
     /** @hide */
     @Override
-    public final void splitFromConference(String callId) {
-        Log.d(this, "splitFromConference(%s)", callId);
+    public final void splitFromConference(String conferenceCallId, String callId) {
+        Log.d(this, "splitFromConference(%s, %s)", conferenceCallId, callId);
 
         Connection connection = findConnectionForAction(callId, "splitFromConference");
         if (connection == NULL_CONNECTION) {
@@ -342,13 +309,6 @@
     }
 
     /**
-     * Returns all connections currently associated with this connection service.
-     */
-    public Collection<Connection> getAllConnections() {
-        return mConnectionById.values();
-    }
-
-    /**
      * Find a set of Subscriptions matching a given handle (e.g. phone number).
      *
      * @param handle A handle (e.g. phone number) with which to connect.
@@ -369,28 +329,8 @@
             Response<ConnectionRequest, Connection> callback) {}
 
     /**
-     * Returns a new or existing conference connection when the the user elects to convert the
-     * specified connection into a conference call. The specified connection can be any connection
-     * which had previously specified itself as conference-capable including both simple connections
-     * and connections previously returned from this method.
-     *
-     * @param connection The connection from which the user opted to start a conference call.
-     * @param token The token to be passed into the response callback.
-     * @param callback The callback for providing the potentially-new conference connection.
-     */
-    public void onCreateConferenceConnection(
-            String token,
-            Connection connection,
-            Response<String, Connection> callback) {}
-
-    /**
      * Create a Connection to match an incoming connection notification.
      *
-     * IMPORTANT: If the incoming connection has a phone number (or other handle) that the user
-     * is not supposed to be able to see (e.g. it is PRESENTATION_RESTRICTED), then a compliant
-     * ConnectionService implementation MUST NOT reveal this phone number as part of the Intent
-     * it sends to notify Telecomm of an incoming connection.
-     *
      * @param request Data encapsulating details of the desired Connection.
      * @param callback A callback for providing the result.
      */
@@ -398,20 +338,6 @@
             ConnectionRequest request,
             Response<ConnectionRequest, Connection> callback) {}
 
-    /**
-     * Notifies that a connection has been added to this connection service and sent to Telecomm.
-     *
-     * @param connection The connection which was added.
-     */
-    public void onConnectionAdded(Connection connection) {}
-
-    /**
-     * Notified that a connection has been removed from this connection service.
-     *
-     * @param connection The connection which was removed.
-     */
-    public void onConnectionRemoved(Connection connection) {}
-
     static String toLogSafePhoneNumber(String number) {
         // For unknown number, log empty string.
         if (number == null) {
@@ -461,14 +387,12 @@
         mConnectionById.put(callId, connection);
         mIdByConnection.put(connection, callId);
         connection.addConnectionListener(mConnectionListener);
-        onConnectionAdded(connection);
     }
 
     private void removeConnection(Connection connection) {
         connection.removeConnectionListener(mConnectionListener);
         mConnectionById.remove(mIdByConnection.get(connection));
         mIdByConnection.remove(connection);
-        onConnectionRemoved(connection);
     }
 
     private Connection findConnectionForAction(String callId, String action) {
diff --git a/telecomm/java/android/telecomm/InCallAdapter.java b/telecomm/java/android/telecomm/InCallAdapter.java
index ce52d19..0bef419 100644
--- a/telecomm/java/android/telecomm/InCallAdapter.java
+++ b/telecomm/java/android/telecomm/InCallAdapter.java
@@ -59,12 +59,10 @@
      * is ported over.
      *
      * @param callId The identifier of the call to reject.
-     * @param rejectWithMessage Whether to reject with a text message.
-     * @param textMessage An optional text message with which to respond.
      */
-    public void rejectCall(String callId, boolean rejectWithMessage, String textMessage) {
+    public void rejectCall(String callId) {
         try {
-            mAdapter.rejectCall(callId, rejectWithMessage, textMessage);
+            mAdapter.rejectCall(callId);
         } catch (RemoteException e) {
         }
     }
@@ -201,14 +199,15 @@
     }
 
     /**
-     * Instructs Telecomm to conference the specified call.
+     * Instructs Telecomm to conference the specified calls together.
      *
      * @param callId The unique ID of the call.
+     * @param callIdToConference The unique ID of the call to conference with.
      * @hide
      */
-    public void conference(String callId) {
+    void conferenceWith(String callId, String callIdToConference) {
         try {
-            mAdapter.conference(callId);
+            mAdapter.conferenceWith(callId, callIdToConference);
         } catch (RemoteException ignored) {
         }
     }
@@ -220,7 +219,7 @@
      * @param callId The unique ID of the call.
      * @hide
      */
-    public void splitFromConference(String callId) {
+    void splitFromConference(String callId) {
         try {
             mAdapter.splitFromConference(callId);
         } catch (RemoteException ignored) {
diff --git a/telecomm/java/android/telecomm/InCallCall.java b/telecomm/java/android/telecomm/InCallCall.java
index 66974f9..b531ccd 100644
--- a/telecomm/java/android/telecomm/InCallCall.java
+++ b/telecomm/java/android/telecomm/InCallCall.java
@@ -33,13 +33,13 @@
     private final CallState mState;
     private final int mDisconnectCauseCode;
     private final String mDisconnectCauseMsg;
-    private final List<String> mCannedSmsResponses;
     private final int mCapabilities;
     private final long mConnectTimeMillis;
     private final Uri mHandle;
     private final GatewayInfo mGatewayInfo;
     private final CallServiceDescriptor mCurrentCallServiceDescriptor;
     private final CallServiceDescriptor mHandoffCallServiceDescriptor;
+    private final List<String> mConferenceCapableCallIds;
     private final String mParentCallId;
     private final List<String> mChildCallIds;
 
@@ -50,16 +50,15 @@
             CallState state,
             int disconnectCauseCode,
             String disconnectCauseMsg,
-            List<String> cannedSmsResponses,
             int capabilities,
             long connectTimeMillis,
             Uri handle,
             GatewayInfo gatewayInfo,
             CallServiceDescriptor descriptor,
             CallServiceDescriptor handoffDescriptor) {
-        this(id, state, disconnectCauseCode, disconnectCauseMsg, cannedSmsResponses,
-                capabilities, connectTimeMillis, handle, gatewayInfo, descriptor, handoffDescriptor,
-                null, Collections.EMPTY_LIST);
+        this(id, state, disconnectCauseCode, disconnectCauseMsg, capabilities, connectTimeMillis,
+                handle, gatewayInfo, descriptor, handoffDescriptor, Collections.EMPTY_LIST, null,
+                Collections.EMPTY_LIST);
     }
 
     /** @hide */
@@ -68,26 +67,26 @@
             CallState state,
             int disconnectCauseCode,
             String disconnectCauseMsg,
-            List<String> cannedSmsResponses,
             int capabilities,
             long connectTimeMillis,
             Uri handle,
             GatewayInfo gatewayInfo,
             CallServiceDescriptor descriptor,
             CallServiceDescriptor handoffDescriptor,
+            List<String> conferenceCapableCallIds,
             String parentCallId,
             List<String> childCallIds) {
         mId = id;
         mState = state;
         mDisconnectCauseCode = disconnectCauseCode;
         mDisconnectCauseMsg = disconnectCauseMsg;
-        mCannedSmsResponses = cannedSmsResponses;
         mCapabilities = capabilities;
         mConnectTimeMillis = connectTimeMillis;
         mHandle = handle;
         mGatewayInfo = gatewayInfo;
         mCurrentCallServiceDescriptor = descriptor;
         mHandoffCallServiceDescriptor = handoffDescriptor;
+        mConferenceCapableCallIds = conferenceCapableCallIds;
         mParentCallId = parentCallId;
         mChildCallIds = childCallIds;
     }
@@ -118,13 +117,6 @@
         return mDisconnectCauseMsg;
     }
 
-    /**
-     * The set of possible text message responses when this call is incoming.
-     */
-    public List<String> getCannedSmsResponses() {
-        return mCannedSmsResponses;
-    }
-
     // Bit mask of actions a call supports, values are defined in {@link CallCapabilities}.
     public int getCapabilities() {
         return mCapabilities;
@@ -159,6 +151,14 @@
     }
 
     /**
+     * The calls with which this call can conference.
+     * @hide
+     */
+    public List<String> getConferenceCapableCallIds() {
+        return mConferenceCapableCallIds;
+    }
+
+    /**
      * The conference call to which this call is conferenced. Null if not conferenced.
      * @hide
      */
@@ -180,25 +180,25 @@
             new Parcelable.Creator<InCallCall> () {
         @Override
         public InCallCall createFromParcel(Parcel source) {
-            ClassLoader classLoader = InCallCall.class.getClassLoader();
             String id = source.readString();
             CallState state = CallState.valueOf(source.readString());
             int disconnectCauseCode = source.readInt();
             String disconnectCauseMsg = source.readString();
-            List<String> cannedSmsResponses = new ArrayList<>();
-            source.readList(cannedSmsResponses, classLoader);
             int capabilities = source.readInt();
             long connectTimeMillis = source.readLong();
+            ClassLoader classLoader = InCallCall.class.getClassLoader();
             Uri handle = source.readParcelable(classLoader);
             GatewayInfo gatewayInfo = source.readParcelable(classLoader);
             CallServiceDescriptor descriptor = source.readParcelable(classLoader);
             CallServiceDescriptor handoffDescriptor = source.readParcelable(classLoader);
+            List<String> conferenceCapableCallIds = new ArrayList<>();
+            source.readList(conferenceCapableCallIds, classLoader);
             String parentCallId = source.readString();
             List<String> childCallIds = new ArrayList<>();
             source.readList(childCallIds, classLoader);
-            return new InCallCall(id, state, disconnectCauseCode, disconnectCauseMsg,
-                    cannedSmsResponses, capabilities, connectTimeMillis, handle, gatewayInfo,
-                    descriptor, handoffDescriptor, parentCallId, childCallIds);
+            return new InCallCall(id, state, disconnectCauseCode, disconnectCauseMsg, capabilities,
+                    connectTimeMillis, handle, gatewayInfo, descriptor, handoffDescriptor,
+                    conferenceCapableCallIds, parentCallId, childCallIds);
         }
 
         @Override
@@ -220,19 +220,14 @@
         destination.writeString(mState.name());
         destination.writeInt(mDisconnectCauseCode);
         destination.writeString(mDisconnectCauseMsg);
-        destination.writeList(mCannedSmsResponses);
         destination.writeInt(mCapabilities);
         destination.writeLong(mConnectTimeMillis);
         destination.writeParcelable(mHandle, 0);
         destination.writeParcelable(mGatewayInfo, 0);
         destination.writeParcelable(mCurrentCallServiceDescriptor, 0);
         destination.writeParcelable(mHandoffCallServiceDescriptor, 0);
+        destination.writeList(mConferenceCapableCallIds);
         destination.writeString(mParentCallId);
         destination.writeList(mChildCallIds);
     }
-
-    @Override
-    public String toString() {
-        return String.format("[%s, parent:%s, children:%s]", mId, mParentCallId, mChildCallIds);
-    }
 }
diff --git a/telecomm/java/android/telecomm/InCallService.java b/telecomm/java/android/telecomm/InCallService.java
index 3a63077..c7dd23a 100644
--- a/telecomm/java/android/telecomm/InCallService.java
+++ b/telecomm/java/android/telecomm/InCallService.java
@@ -203,7 +203,7 @@
      * {@link #setPostDial(String,String)} state but is now waiting for user confirmation before the
      * remaining digits can be sent. Normal transitions are to {@link #setPostDial(String,String)}
      * when the user asks Telecomm to proceed with the post-dial sequence and the in-call app
-     * informs Telecomm of this by invoking {@link InCallAdapter#postDialContinue(String)}.
+     * informs Telecomm of this by invoking {@link InCallAdapter#postDialContinue(String,boolean)}.
      *
      * @param callId The identifier of the call changing state.
      * @param remaining The remaining postdial string to be dialed.
diff --git a/telecomm/java/android/telecomm/package.html b/telecomm/java/android/telecomm/package.html
deleted file mode 100644
index 1c9bf9d..0000000
--- a/telecomm/java/android/telecomm/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<html>
-<body>
-    {@hide}
-</body>
-</html>
diff --git a/telecomm/java/com/android/internal/telecomm/ICallService.aidl b/telecomm/java/com/android/internal/telecomm/ICallService.aidl
index 827f331..9139aa6 100644
--- a/telecomm/java/com/android/internal/telecomm/ICallService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ICallService.aidl
@@ -56,9 +56,9 @@
 
     void stopDtmfTone(String callId);
 
-    void conference(String conferenceCallId, String callId);
+    void addToConference(String conferenceCallId, in List<String> callIds);
 
-    void splitFromConference(String callId);
+    void splitFromConference(String conferenceCallId, String callId);
 
     void onPostDialContinue(String callId, boolean proceed);
 }
diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
index ab269bb..b380293 100644
--- a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
@@ -47,13 +47,13 @@
 
     void setRequestingRingback(String callId, boolean ringing);
 
-    void setCanConference(String callId, boolean canConference);
+    void setCanConferenceWith(String callId, in List<String> conferenceCapableCallIds);
 
-    void setIsConferenced(String callId, String conferenceCallId);
-
-    void addConferenceCall(String callId, in CallInfo callInfo);
+    void setIsConferenced(String conferenceCallId, String callId, boolean isConferenced);
 
     void removeCall(String callId);
 
     void onPostDialWait(String callId, String remaining);
+
+    void handoffCall(String callId);
 }
diff --git a/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
index b66995a..f144043 100644
--- a/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
@@ -28,7 +28,7 @@
 oneway interface IInCallAdapter {
     void answerCall(String callId);
 
-    void rejectCall(String callId, boolean rejectWithMessage, String textMessage);
+    void rejectCall(String callId);
 
     void disconnectCall(String callId);
 
@@ -48,7 +48,7 @@
 
     void handoffCall(String callId);
 
-    void conference(String callId);
+    void conferenceWith(String callId, String callIdToConference);
 
     void splitFromConference(String callId);
 }
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 604b895..d2044be 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -18,8 +18,6 @@
 
 /**
  * Contains disconnect call causes generated by the framework and the RIL.
- *
- * @hide
  */
 public class DisconnectCause {
 
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 6996659..ed7f6b8 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -1749,7 +1749,9 @@
      * is currently in.
      */
     public static boolean isLocalEmergencyNumber(Context context, String number) {
-        return isLocalEmergencyNumberInternal(context, number, true /* useExactMatch */);
+        return isLocalEmergencyNumberInternal(context,
+                                              number,
+                                              true /* useExactMatch */);
     }
 
     /**
@@ -1775,7 +1777,9 @@
      * @hide
      */
     public static boolean isPotentialLocalEmergencyNumber(Context context, String number) {
-        return isLocalEmergencyNumberInternal(context, number, false /* useExactMatch */);
+        return isLocalEmergencyNumberInternal(context,
+                                              number,
+                                              false /* useExactMatch */);
     }
 
     /**
@@ -1794,8 +1798,9 @@
      *
      * @see android.location.CountryDetector
      */
-    private static boolean isLocalEmergencyNumberInternal(Context context, String number,
-            boolean useExactMatch) {
+    private static boolean isLocalEmergencyNumberInternal(Context context,
+                                                          String number,
+                                                          boolean useExactMatch) {
         String countryIso;
         CountryDetector detector = (CountryDetector) context.getSystemService(
                 Context.COUNTRY_DETECTOR);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 949bc5d..3f65bca 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1743,6 +1743,224 @@
     }
 
     /**
+     * Opens a logical channel to the ICC card.
+     *
+     * Input parameters equivalent to TS 27.007 AT+CCHO command.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#SIM_COMMUNICATION SIM_COMMUNICATION}
+     *
+     * @param AID Application id. See ETSI 102.221 and 101.220.
+     * @return The logical channel id which is negative on error.
+     *
+     * @hide
+     */
+    public int iccOpenLogicalChannel(String AID) {
+        try {
+            return getITelephony().iccOpenLogicalChannel(AID);
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        return -1;
+    }
+
+    /**
+     * Closes a previously opened logical channel to the ICC card.
+     *
+     * Input parameters equivalent to TS 27.007 AT+CCHC command.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#SIM_COMMUNICATION SIM_COMMUNICATION}
+     *
+     * @param channel is the channel id to be closed as retruned by a successful
+     *            iccOpenLogicalChannel.
+     * @return true if the channel was closed successfully.
+     *
+     * @hide
+     */
+    public boolean iccCloseLogicalChannel(int channel) {
+        try {
+            return getITelephony().iccCloseLogicalChannel(channel);
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        return false;
+    }
+
+    /**
+     * Transmit an APDU to the ICC card over a logical channel.
+     *
+     * Input parameters equivalent to TS 27.007 AT+CGLA command.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#SIM_COMMUNICATION SIM_COMMUNICATION}
+     *
+     * @param channel is the channel id to be closed as returned by a successful
+     *            iccOpenLogicalChannel.
+     * @param cla Class of the APDU command.
+     * @param instruction Instruction of the APDU command.
+     * @param p1 P1 value of the APDU command.
+     * @param p2 P2 value of the APDU command.
+     * @param p3 P3 value of the APDU command. If p3 is negative a 4 byte APDU
+     *            is sent to the SIM.
+     * @param data Data to be sent with the APDU.
+     * @return The APDU response from the ICC card with the status appended at
+     *            the end. If an error occurs, an empty string is returned.
+     *
+     * @hide
+     */
+    public String iccTransmitApduLogicalChannel(int channel, int cla,
+            int instruction, int p1, int p2, int p3, String data) {
+        try {
+            return getITelephony().iccTransmitApduLogicalChannel(channel, cla,
+                    instruction, p1, p2, p3, data);
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        return "";
+    }
+
+    /**
+     * Send ENVELOPE to the SIM and return the response.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#SIM_COMMUNICATION SIM_COMMUNICATION}
+     *
+     * @param content String containing SAT/USAT response in hexadecimal
+     *                format starting with command tag. See TS 102 223 for
+     *                details.
+     * @return The APDU response from the ICC card, with the last 4 bytes
+     *         being the status word. If the command fails, returns an empty
+     *         string.
+     *
+     * @hide
+     */
+    public String sendEnvelopeWithStatus(String content) {
+        try {
+            return getITelephony().sendEnvelopeWithStatus(content);
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        return "";
+    }
+
+    /**
+     * Read one of the NV items defined in {@link com.android.internal.telephony.RadioNVItems}.
+     * Used for device configuration by some CDMA operators.
+     *
+     * @param itemID the ID of the item to read.
+     * @return the NV item as a String, or null on any failure.
+     * @hide
+     */
+    public String nvReadItem(int itemID) {
+        try {
+            return getITelephony().nvReadItem(itemID);
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "nvReadItem RemoteException", ex);
+        } catch (NullPointerException ex) {
+            Rlog.e(TAG, "nvReadItem NPE", ex);
+        }
+        return "";
+    }
+
+
+    /**
+     * Write one of the NV items defined in {@link com.android.internal.telephony.RadioNVItems}.
+     * Used for device configuration by some CDMA operators.
+     *
+     * @param itemID the ID of the item to read.
+     * @param itemValue the value to write, as a String.
+     * @return true on success; false on any failure.
+     * @hide
+     */
+    public boolean nvWriteItem(int itemID, String itemValue) {
+        try {
+            return getITelephony().nvWriteItem(itemID, itemValue);
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "nvWriteItem RemoteException", ex);
+        } catch (NullPointerException ex) {
+            Rlog.e(TAG, "nvWriteItem NPE", ex);
+        }
+        return false;
+    }
+
+    /**
+     * Update the CDMA Preferred Roaming List (PRL) in the radio NV storage.
+     * Used for device configuration by some CDMA operators.
+     *
+     * @param preferredRoamingList byte array containing the new PRL.
+     * @return true on success; false on any failure.
+     * @hide
+     */
+    public boolean nvWriteCdmaPrl(byte[] preferredRoamingList) {
+        try {
+            return getITelephony().nvWriteCdmaPrl(preferredRoamingList);
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "nvWriteCdmaPrl RemoteException", ex);
+        } catch (NullPointerException ex) {
+            Rlog.e(TAG, "nvWriteCdmaPrl NPE", ex);
+        }
+        return false;
+    }
+
+    /**
+     * Perform the specified type of NV config reset. The radio will be taken offline
+     * and the device must be rebooted after the operation. Used for device
+     * configuration by some CDMA operators.
+     *
+     * @param resetType reset type: 1: reload NV reset, 2: erase NV reset, 3: factory NV reset
+     * @return true on success; false on any failure.
+     * @hide
+     */
+    public boolean nvResetConfig(int resetType) {
+        try {
+            return getITelephony().nvResetConfig(resetType);
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "nvResetConfig RemoteException", ex);
+        } catch (NullPointerException ex) {
+            Rlog.e(TAG, "nvResetConfig NPE", ex);
+        }
+        return false;
+    }
+
+    /**
+     * Get the preferred network type.
+     * Used for device configuration by some CDMA operators.
+     *
+     * @return the preferred network type, defined in RILConstants.java.
+     * @hide
+     */
+    public int getPreferredNetworkType() {
+        try {
+            return getITelephony().getPreferredNetworkType();
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "getPreferredNetworkType RemoteException", ex);
+        } catch (NullPointerException ex) {
+            Rlog.e(TAG, "getPreferredNetworkType NPE", ex);
+        }
+        return -1;
+    }
+
+    /**
+     * Set the preferred network type.
+     * Used for device configuration by some CDMA operators.
+     *
+     * @param networkType the preferred network type, defined in RILConstants.java.
+     * @return true on success; false on any failure.
+     * @hide
+     */
+    public boolean setPreferredNetworkType(int networkType) {
+        try {
+            return getITelephony().setPreferredNetworkType(networkType);
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "setPreferredNetworkType RemoteException", ex);
+        } catch (NullPointerException ex) {
+            Rlog.e(TAG, "setPreferredNetworkType NPE", ex);
+        }
+        return false;
+    }
+
+    /**
      * Expose the rest of ITelephony to @SystemApi
      */
 
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 59bdf64..874279b 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -98,6 +98,7 @@
     public static final int CMD_IS_PROVISIONING_APN = BASE + 38;
     public static final int EVENT_PROVISIONING_APN_ALARM = BASE + 39;
     public static final int CMD_NET_STAT_POLL = BASE + 40;
+    public static final int EVENT_DATA_RAT_CHANGED = BASE + 41;
 
     /***** Constants *****/
 
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index 3e8db06..6fc64ae 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -119,6 +119,19 @@
             in PendingIntent sentIntent, in PendingIntent deliveryIntent);
 
     /**
+     * Inject an SMS PDU into the android platform.
+     *
+     * @param pdu is the byte array of pdu to be injected into android application framework
+     * @param format is the format of SMS pdu (android.telephony.SmsMessage.FORMAT_3GPP or
+     * android.telephony.SmsMessage.FORMAT_3GPP2)
+     * @param receivedIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully received by the
+     *  android application framework. This intent is broadcasted at
+     *  the same time an SMS received from radio is acknowledged back.
+     */
+    void injectSmsPdu(in byte[] pdu, String format, in PendingIntent receivedIntent);
+
+    /**
      * Send a multi-part text based SMS.
      *
      * @param destinationAddress the address to send the message to
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index f9462f2..acaa8de 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -308,6 +308,114 @@
     void setCellInfoListRate(int rateInMillis);
 
     /**
+     * Opens a logical channel to the ICC card.
+     *
+     * Input parameters equivalent to TS 27.007 AT+CCHO command.
+     *
+     * @param AID Application id. See ETSI 102.221 and 101.220.
+     * @return The logical channel id which is set to -1 on error.
+     */
+    int iccOpenLogicalChannel(String AID);
+
+    /**
+     * Closes a previously opened logical channel to the ICC card.
+     *
+     * Input parameters equivalent to TS 27.007 AT+CCHC command.
+     *
+     * @param channel is the channel id to be closed as retruned by a
+     *            successful iccOpenLogicalChannel.
+     * @return true if the channel was closed successfully.
+     */
+    boolean iccCloseLogicalChannel(int channel);
+
+    /**
+     * Transmit an APDU to the ICC card over a logical channel.
+     *
+     * Input parameters equivalent to TS 27.007 AT+CGLA command.
+     *
+     * @param channel is the channel id to be closed as retruned by a
+     *            successful iccOpenLogicalChannel.
+     * @param cla Class of the APDU command.
+     * @param instruction Instruction of the APDU command.
+     * @param p1 P1 value of the APDU command.
+     * @param p2 P2 value of the APDU command.
+     * @param p3 P3 value of the APDU command. If p3 is negative a 4 byte APDU
+     *            is sent to the SIM.
+     * @param data Data to be sent with the APDU.
+     * @return The APDU response from the ICC card with the status appended at
+     *            the end. If an error occurs, an empty string is returned.
+     */
+    String iccTransmitApduLogicalChannel(int channel, int cla, int instruction,
+            int p1, int p2, int p3, String data);
+
+    /**
+     * Send ENVELOPE to the SIM and returns the response.
+     *
+     * @param contents  String containing SAT/USAT response in hexadecimal
+     *                  format starting with command tag. See TS 102 223 for
+     *                  details.
+     * @return The APDU response from the ICC card, with the last 4 bytes
+     *         being the status word. If the command fails, returns an empty
+     *         string.
+     */
+    String sendEnvelopeWithStatus(String content);
+
+    /**
+     * Read one of the NV items defined in {@link RadioNVItems} / {@code ril_nv_items.h}.
+     * Used for device configuration by some CDMA operators.
+     *
+     * @param itemID the ID of the item to read.
+     * @return the NV item as a String, or null on any failure.
+     */
+    String nvReadItem(int itemID);
+
+    /**
+     * Write one of the NV items defined in {@link RadioNVItems} / {@code ril_nv_items.h}.
+     * Used for device configuration by some CDMA operators.
+     *
+     * @param itemID the ID of the item to read.
+     * @param itemValue the value to write, as a String.
+     * @return true on success; false on any failure.
+     */
+    boolean nvWriteItem(int itemID, String itemValue);
+
+    /**
+     * Update the CDMA Preferred Roaming List (PRL) in the radio NV storage.
+     * Used for device configuration by some CDMA operators.
+     *
+     * @param preferredRoamingList byte array containing the new PRL.
+     * @return true on success; false on any failure.
+     */
+    boolean nvWriteCdmaPrl(in byte[] preferredRoamingList);
+
+    /**
+     * Perform the specified type of NV config reset. The radio will be taken offline
+     * and the device must be rebooted after the operation. Used for device
+     * configuration by some CDMA operators.
+     *
+     * @param resetType the type of reset to perform (1 == factory reset; 2 == NV-only reset).
+     * @return true on success; false on any failure.
+     */
+    boolean nvResetConfig(int resetType);
+
+    /*
+     * Get the preferred network type.
+     * Used for device configuration by some CDMA operators.
+     *
+     * @return the preferred network type, defined in RILConstants.java.
+     */
+    int getPreferredNetworkType();
+
+    /**
+     * Set the preferred network type.
+     * Used for device configuration by some CDMA operators.
+     *
+     * @param networkType the preferred network type, defined in RILConstants.java.
+     * @return true on success; false on any failure.
+     */
+    boolean setPreferredNetworkType(int networkType);
+
+    /**
      * User enable/disable Mobile Data.
      *
      * @param enable true to turn on, else false
@@ -321,4 +429,3 @@
      */
     boolean getDataEnabled();
 }
-
diff --git a/test-runner/src/android/test/MoreAsserts.java b/test-runner/src/android/test/MoreAsserts.java
index fb0faba..3364895 100644
--- a/test-runner/src/android/test/MoreAsserts.java
+++ b/test-runner/src/android/test/MoreAsserts.java
@@ -128,6 +128,33 @@
     }
 
     /**
+     * @hide Asserts that array {@code actual} is the same size and every element equals
+     * those in array {@code expected}. On failure, message indicates first
+     * specific element mismatch.
+     */
+    public static void assertEquals(
+            String message, long[] expected, long[] actual) {
+        if (expected.length != actual.length) {
+            failWrongLength(message, expected.length, actual.length);
+        }
+        for (int i = 0; i < expected.length; i++) {
+            if (expected[i] != actual[i]) {
+                failWrongElement(message, i, expected[i], actual[i]);
+            }
+        }
+    }
+
+    /**
+     * @hide Asserts that array {@code actual} is the same size and every element equals
+     * those in array {@code expected}. On failure, message indicates first
+     * specific element mismatch.
+     */
+    public static void assertEquals(long[] expected, long[] actual) {
+        assertEquals(null, expected, actual);
+    }
+
+
+    /**
      * Asserts that array {@code actual} is the same size and every element equals
      * those in array {@code expected}. On failure, message indicates first
      * specific element mismatch.
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 3190fb0..17db1b4 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -707,8 +707,8 @@
      * @hide
      */
     @Override
-    public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int userIdOrig,
-            int userIdDest) {
+    public void addCrossProfileIntentFilter(IntentFilter filter, boolean removable,
+            int sourceUserId, int targetUserId) {
         throw new UnsupportedOperationException();
     }
 
@@ -716,7 +716,24 @@
      * @hide
      */
     @Override
-    public void clearForwardingIntentFilters(int userIdOrig) {
+    public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int sourceUserId,
+            int targetUserId) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void clearCrossProfileIntentFilters(int sourceUserId) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void clearForwardingIntentFilters(int sourceUserId) {
         throw new UnsupportedOperationException();
     }
 
diff --git a/tests/Camera2Tests/CameraToo/Android.mk b/tests/Camera2Tests/CameraToo/Android.mk
new file mode 100644
index 0000000..7e5911d
--- /dev/null
+++ b/tests/Camera2Tests/CameraToo/Android.mk
@@ -0,0 +1,23 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := CameraToo
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+
+include $(BUILD_PACKAGE)
diff --git a/tests/Camera2Tests/CameraToo/AndroidManifest.xml b/tests/Camera2Tests/CameraToo/AndroidManifest.xml
new file mode 100644
index 0000000..a92b5d8
--- /dev/null
+++ b/tests/Camera2Tests/CameraToo/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+    Copyright (C) 2014 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.camera2.cameratoo">
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <application android:label="CameraToo">
+        <activity
+            android:name=".CameraTooActivity"
+            android:screenOrientation="portrait">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/Camera2Tests/CameraToo/res/layout/mainactivity.xml b/tests/Camera2Tests/CameraToo/res/layout/mainactivity.xml
new file mode 100644
index 0000000..f93f177
--- /dev/null
+++ b/tests/Camera2Tests/CameraToo/res/layout/mainactivity.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+    Copyright (C) 2014 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<SurfaceView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/mainSurfaceView"
+    android:layout_height="fill_parent"
+    android:layout_width="fill_parent"
+    android:onClick="onClickOnSurfaceView" />
diff --git a/tests/Camera2Tests/CameraToo/src/com/example/android/camera2/cameratoo/CameraTooActivity.java b/tests/Camera2Tests/CameraToo/src/com/example/android/camera2/cameratoo/CameraTooActivity.java
new file mode 100644
index 0000000..c630bad
--- /dev/null
+++ b/tests/Camera2Tests/CameraToo/src/com/example/android/camera2/cameratoo/CameraTooActivity.java
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.camera2.cameratoo;
+
+import android.app.Activity;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.util.Size;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * A basic demonstration of how to write a point-and-shoot camera app against the new
+ * android.hardware.camera2 API.
+ */
+public class CameraTooActivity extends Activity {
+    /** Output files will be saved as /sdcard/Pictures/cameratoo*.jpg */
+    static final String CAPTURE_FILENAME_PREFIX = "cameratoo";
+    /** Tag to distinguish log prints. */
+    static final String TAG = "CameraToo";
+
+    /** An additional thread for running tasks that shouldn't block the UI. */
+    HandlerThread mBackgroundThread;
+    /** Handler for running tasks in the background. */
+    Handler mBackgroundHandler;
+    /** Handler for running tasks on the UI thread. */
+    Handler mForegroundHandler;
+    /** View for displaying the camera preview. */
+    SurfaceView mSurfaceView;
+    /** Used to retrieve the captured image when the user takes a snapshot. */
+    ImageReader mCaptureBuffer;
+    /** Handle to the Android camera services. */
+    CameraManager mCameraManager;
+    /** The specific camera device that we're using. */
+    CameraDevice mCamera;
+    /** Our image capture session. */
+    CameraCaptureSession mCaptureSession;
+
+    /**
+     * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
+     * width and height are at least as large as the respective requested values.
+     * @param choices The list of sizes that the camera supports for the intended output class
+     * @param width The minimum desired width
+     * @param height The minimum desired height
+     * @return The optimal {@code Size}, or an arbitrary one if none were big enough
+     */
+    static Size chooseBigEnoughSize(Size[] choices, int width, int height) {
+        // Collect the supported resolutions that are at least as big as the preview Surface
+        List<Size> bigEnough = new ArrayList<Size>();
+        for (Size option : choices) {
+            if (option.getWidth() >= width && option.getHeight() >= height) {
+                bigEnough.add(option);
+            }
+        }
+
+        // Pick the smallest of those, assuming we found any
+        if (bigEnough.size() > 0) {
+            return Collections.min(bigEnough, new CompareSizesByArea());
+        } else {
+            Log.e(TAG, "Couldn't find any suitable preview size");
+            return choices[0];
+        }
+    }
+
+    /**
+     * Compares two {@code Size}s based on their areas.
+     */
+    static class CompareSizesByArea implements Comparator<Size> {
+        @Override
+        public int compare(Size lhs, Size rhs) {
+            // We cast here to ensure the multiplications won't overflow
+            return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
+                    (long) rhs.getWidth() * rhs.getHeight());
+        }
+    }
+
+    /**
+     * Called when our {@code Activity} gains focus. <p>Starts initializing the camera.</p>
+     */
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        // Start a background thread to manage camera requests
+        mBackgroundThread = new HandlerThread("background");
+        mBackgroundThread.start();
+        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
+        mForegroundHandler = new Handler(getMainLooper());
+
+        mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
+
+        // Inflate the SurfaceView, set it as the main layout, and attach a listener
+        View layout = getLayoutInflater().inflate(R.layout.mainactivity, null);
+        mSurfaceView = (SurfaceView) layout.findViewById(R.id.mainSurfaceView);
+        mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
+        setContentView(mSurfaceView);
+
+        // Control flow continues in mSurfaceHolderCallback.surfaceChanged()
+    }
+
+    /**
+     * Called when our {@code Activity} loses focus. <p>Tears everything back down.</p>
+     */
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        try {
+            // Ensure SurfaceHolderCallback#surfaceChanged() will run again if the user returns
+            mSurfaceView.getHolder().setFixedSize(/*width*/0, /*height*/0);
+
+            // Cancel any stale preview jobs
+            if (mCaptureSession != null) {
+                mCaptureSession.close();
+                mCaptureSession = null;
+            }
+        } finally {
+            if (mCamera != null) {
+                mCamera.close();
+                mCamera = null;
+            }
+        }
+
+        // Finish processing posted messages, then join on the handling thread
+        mBackgroundThread.quitSafely();
+        try {
+            mBackgroundThread.join();
+        } catch (InterruptedException ex) {
+            Log.e(TAG, "Background worker thread was interrupted while joined", ex);
+        }
+
+        // Close the ImageReader now that the background thread has stopped
+        if (mCaptureBuffer != null) mCaptureBuffer.close();
+    }
+
+    /**
+     * Called when the user clicks on our {@code SurfaceView}, which has ID {@code mainSurfaceView}
+     * as defined in the {@code mainactivity.xml} layout file. <p>Captures a full-resolution image
+     * and saves it to permanent storage.</p>
+     */
+    public void onClickOnSurfaceView(View v) {
+        if (mCaptureSession != null) {
+            try {
+                CaptureRequest.Builder requester =
+                        mCamera.createCaptureRequest(mCamera.TEMPLATE_STILL_CAPTURE);
+                requester.addTarget(mCaptureBuffer.getSurface());
+                try {
+                    // This handler can be null because we aren't actually attaching any callback
+                    mCaptureSession.capture(requester.build(), /*listener*/null, /*handler*/null);
+                } catch (CameraAccessException ex) {
+                    Log.e(TAG, "Failed to file actual capture request", ex);
+                }
+            } catch (CameraAccessException ex) {
+                Log.e(TAG, "Failed to build actual capture request", ex);
+            }
+        } else {
+            Log.e(TAG, "User attempted to perform a capture outside our session");
+        }
+
+        // Control flow continues in mImageCaptureListener.onImageAvailable()
+    }
+
+    /**
+     * Callbacks invoked upon state changes in our {@code SurfaceView}.
+     */
+    final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
+        /** The camera device to use, or null if we haven't yet set a fixed surface size. */
+        private String mCameraId;
+
+        /** Whether we received a change callback after setting our fixed surface size. */
+        private boolean mGotSecondCallback;
+
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            // This is called every time the surface returns to the foreground
+            Log.i(TAG, "Surface created");
+            mCameraId = null;
+            mGotSecondCallback = false;
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            Log.i(TAG, "Surface destroyed");
+            holder.removeCallback(this);
+            // We don't stop receiving callbacks forever because onResume() will reattach us
+        }
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            // On the first invocation, width and height were automatically set to the view's size
+            if (mCameraId == null) {
+                // Find the device's back-facing camera and set the destination buffer sizes
+                try {
+                    for (String cameraId : mCameraManager.getCameraIdList()) {
+                        CameraCharacteristics cameraCharacteristics =
+                                mCameraManager.getCameraCharacteristics(cameraId);
+                        if (cameraCharacteristics.get(cameraCharacteristics.LENS_FACING) ==
+                                CameraCharacteristics.LENS_FACING_BACK) {
+                            Log.i(TAG, "Found a back-facing camera");
+                            StreamConfigurationMap info = cameraCharacteristics
+                                    .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+                            // Bigger is better when it comes to saving our image
+                            Size largestSize = Collections.max(
+                                    Arrays.asList(info.getOutputSizes(ImageFormat.JPEG)),
+                                    new CompareSizesByArea());
+
+                            // Prepare an ImageReader in case the user wants to capture images
+                            Log.i(TAG, "Capture size: " + largestSize);
+                            mCaptureBuffer = ImageReader.newInstance(largestSize.getWidth(),
+                                    largestSize.getHeight(), ImageFormat.JPEG, /*maxImages*/2);
+                            mCaptureBuffer.setOnImageAvailableListener(
+                                    mImageCaptureListener, mBackgroundHandler);
+
+                            // Danger, W.R.! Attempting to use too large a preview size could
+                            // exceed the camera bus' bandwidth limitation, resulting in
+                            // gorgeous previews but the storage of garbage capture data.
+                            Log.i(TAG, "SurfaceView size: " +
+                                    mSurfaceView.getWidth() + 'x' + mSurfaceView.getHeight());
+                            Size optimalSize = chooseBigEnoughSize(
+                                    info.getOutputSizes(SurfaceHolder.class), width, height);
+
+                            // Set the SurfaceHolder to use the camera's largest supported size
+                            Log.i(TAG, "Preview size: " + optimalSize);
+                            SurfaceHolder surfaceHolder = mSurfaceView.getHolder();
+                            surfaceHolder.setFixedSize(optimalSize.getWidth(),
+                                    optimalSize.getHeight());
+
+                            mCameraId = cameraId;
+                            return;
+
+                            // Control flow continues with this method one more time
+                            // (since we just changed our own size)
+                        }
+                    }
+                } catch (CameraAccessException ex) {
+                    Log.e(TAG, "Unable to list cameras", ex);
+                }
+
+                Log.e(TAG, "Didn't find any back-facing cameras");
+            // This is the second time the method is being invoked: our size change is complete
+            } else if (!mGotSecondCallback) {
+                if (mCamera != null) {
+                    Log.e(TAG, "Aborting camera open because it hadn't been closed");
+                    return;
+                }
+
+                // Open the camera device
+                try {
+                    mCameraManager.openCamera(mCameraId, mCameraStateListener,
+                            mBackgroundHandler);
+                } catch (CameraAccessException ex) {
+                    Log.e(TAG, "Failed to configure output surface", ex);
+                }
+                mGotSecondCallback = true;
+
+                // Control flow continues in mCameraStateListener.onOpened()
+            }
+        }};
+
+    /**
+     * Calledbacks invoked upon state changes in our {@code CameraDevice}. <p>These are run on
+     * {@code mBackgroundThread}.</p>
+     */
+    final CameraDevice.StateListener mCameraStateListener =
+            new CameraDevice.StateListener() {
+        @Override
+        public void onOpened(CameraDevice camera) {
+            Log.i(TAG, "Successfully opened camera");
+            mCamera = camera;
+            try {
+                List<Surface> outputs = Arrays.asList(
+                        mSurfaceView.getHolder().getSurface(), mCaptureBuffer.getSurface());
+                camera.createCaptureSession(outputs, mCaptureSessionListener,
+                        mBackgroundHandler);
+            } catch (CameraAccessException ex) {
+                Log.e(TAG, "Failed to create a capture session", ex);
+            }
+
+            // Control flow continues in mCaptureSessionListener.onConfigured()
+        }
+
+        @Override
+        public void onDisconnected(CameraDevice camera) {
+            Log.e(TAG, "Camera was disconnected");
+        }
+
+        @Override
+        public void onError(CameraDevice camera, int error) {
+            Log.e(TAG, "State error on device '" + camera.getId() + "': code " + error);
+        }};
+
+    /**
+     * Callbacks invoked upon state changes in our {@code CameraCaptureSession}. <p>These are run on
+     * {@code mBackgroundThread}.</p>
+     */
+    final CameraCaptureSession.StateListener mCaptureSessionListener =
+            new CameraCaptureSession.StateListener() {
+        @Override
+        public void onConfigured(CameraCaptureSession session) {
+            Log.i(TAG, "Finished configuring camera outputs");
+            mCaptureSession = session;
+
+            SurfaceHolder holder = mSurfaceView.getHolder();
+            if (holder != null) {
+                try {
+                    // Build a request for preview footage
+                    CaptureRequest.Builder requestBuilder =
+                            mCamera.createCaptureRequest(mCamera.TEMPLATE_PREVIEW);
+                    requestBuilder.addTarget(holder.getSurface());
+                    CaptureRequest previewRequest = requestBuilder.build();
+
+                    // Start displaying preview images
+                    try {
+                        session.setRepeatingRequest(previewRequest, /*listener*/null,
+                                /*handler*/null);
+                    } catch (CameraAccessException ex) {
+                        Log.e(TAG, "Failed to make repeating preview request", ex);
+                    }
+                } catch (CameraAccessException ex) {
+                    Log.e(TAG, "Failed to build preview request", ex);
+                }
+            }
+            else {
+                Log.e(TAG, "Holder didn't exist when trying to formulate preview request");
+            }
+        }
+
+        @Override
+        public void onClosed(CameraCaptureSession session) {
+            mCaptureSession = null;
+        }
+
+        @Override
+        public void onConfigureFailed(CameraCaptureSession session) {
+            Log.e(TAG, "Configuration error on device '" + mCamera.getId());
+        }};
+
+    /**
+     * Callback invoked when we've received a JPEG image from the camera.
+     */
+    final ImageReader.OnImageAvailableListener mImageCaptureListener =
+            new ImageReader.OnImageAvailableListener() {
+        @Override
+        public void onImageAvailable(ImageReader reader) {
+            // Save the image once we get a chance
+            mBackgroundHandler.post(new CapturedImageSaver(reader.acquireNextImage()));
+
+            // Control flow continues in CapturedImageSaver#run()
+        }};
+
+    /**
+     * Deferred processor responsible for saving snapshots to disk. <p>This is run on
+     * {@code mBackgroundThread}.</p>
+     */
+    static class CapturedImageSaver implements Runnable {
+        /** The image to save. */
+        private Image mCapture;
+
+        public CapturedImageSaver(Image capture) {
+            mCapture = capture;
+        }
+
+        @Override
+        public void run() {
+            try {
+                // Choose an unused filename under the Pictures/ directory
+                File file = File.createTempFile(CAPTURE_FILENAME_PREFIX, ".jpg",
+                        Environment.getExternalStoragePublicDirectory(
+                                Environment.DIRECTORY_PICTURES));
+                try (FileOutputStream ostream = new FileOutputStream(file)) {
+                    Log.i(TAG, "Retrieved image is" +
+                            (mCapture.getFormat() == ImageFormat.JPEG ? "" : "n't") + " a JPEG");
+                    ByteBuffer buffer = mCapture.getPlanes()[0].getBuffer();
+                    Log.i(TAG, "Captured image size: " +
+                            mCapture.getWidth() + 'x' + mCapture.getHeight());
+
+                    // Write the image out to the chosen file
+                    byte[] jpeg = new byte[buffer.remaining()];
+                    buffer.get(jpeg);
+                    ostream.write(jpeg);
+                } catch (FileNotFoundException ex) {
+                    Log.e(TAG, "Unable to open output file for writing", ex);
+                } catch (IOException ex) {
+                    Log.e(TAG, "Failed to write the image to the output file", ex);
+                }
+            } catch (IOException ex) {
+                Log.e(TAG, "Unable to create a new output file", ex);
+            } finally {
+                mCapture.close();
+            }
+        }
+    }
+}
diff --git a/tests/Camera2Tests/CameraToo/tests/Android.mk b/tests/Camera2Tests/CameraToo/tests/Android.mk
new file mode 100644
index 0000000..0b58243
--- /dev/null
+++ b/tests/Camera2Tests/CameraToo/tests/Android.mk
@@ -0,0 +1,25 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := CameraTooTests
+LOCAL_INSTRUMENTATION_FOR := CameraToo
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test mockito-target
+
+include $(BUILD_PACKAGE)
diff --git a/tests/Camera2Tests/CameraToo/tests/AndroidManifest.xml b/tests/Camera2Tests/CameraToo/tests/AndroidManifest.xml
new file mode 100644
index 0000000..30210ba
--- /dev/null
+++ b/tests/Camera2Tests/CameraToo/tests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+    Copyright (C) 2014 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.camera2.cameratoo.tests">
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <application android:label="CameraToo">
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.example.android.camera2.cameratoo"
+        android:label="CameraToo tests" />
+</manifest>
diff --git a/tests/Camera2Tests/CameraToo/tests/src/com/example/android/camera2/cameratoo/CameraTooTest.java b/tests/Camera2Tests/CameraToo/tests/src/com/example/android/camera2/cameratoo/CameraTooTest.java
new file mode 100644
index 0000000..3acca5a
--- /dev/null
+++ b/tests/Camera2Tests/CameraToo/tests/src/com/example/android/camera2/cameratoo/CameraTooTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.camera2.cameratoo;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.media.Image;
+import android.os.Environment;
+import android.util.Size;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.example.android.camera2.cameratoo.CameraTooActivity;
+import org.junit.Test;
+
+public class CameraTooTest {
+    private <T> void assertComparatorEq(T lhs, T rhs, Comparator<T> rel) {
+        assertEquals(String.format("%s should be equal to %s", lhs, rhs), rel.compare(lhs, rhs), 0);
+        assertEquals(String.format("%s should be equal to %s (reverse check)", lhs, rhs),
+                rel.compare(rhs, lhs), 0);
+    }
+
+    private <T> void assertComparatorLt(T lhs, T rhs, Comparator<T> rel) {
+        assertTrue(String.format("%s should be less than %s", lhs, rhs), rel.compare(lhs, rhs) < 0);
+        assertTrue(String.format("%s should be less than %s (reverse check)", lhs, rhs),
+                rel.compare(rhs, lhs) > 0);
+    }
+
+    @Test
+    public void compareSizesByArea() {
+        Size empty = new Size(0, 0), fatAndFlat = new Size(100, 0), tallAndThin = new Size(0, 100);
+        Size smallSquare = new Size(4, 4), horizRect = new Size(8, 2), vertRect = new Size(2, 8);
+        Size largeSquare = new Size(5, 5);
+        Comparator<Size> rel = new CameraTooActivity.CompareSizesByArea();
+
+        assertComparatorEq(empty, fatAndFlat, rel);
+        assertComparatorEq(empty, tallAndThin, rel);
+        assertComparatorEq(fatAndFlat, empty, rel);
+        assertComparatorEq(fatAndFlat, tallAndThin, rel);
+        assertComparatorEq(tallAndThin, empty, rel);
+        assertComparatorEq(tallAndThin, fatAndFlat, rel);
+
+        assertComparatorEq(smallSquare, horizRect, rel);
+        assertComparatorEq(smallSquare, vertRect, rel);
+        assertComparatorEq(horizRect, smallSquare, rel);
+        assertComparatorEq(horizRect, vertRect, rel);
+        assertComparatorEq(vertRect, smallSquare, rel);
+        assertComparatorEq(vertRect, horizRect, rel);
+
+        assertComparatorLt(empty, smallSquare, rel);
+        assertComparatorLt(empty, horizRect, rel);
+        assertComparatorLt(empty, vertRect, rel);
+
+        assertComparatorLt(fatAndFlat, smallSquare, rel);
+        assertComparatorLt(fatAndFlat, horizRect, rel);
+        assertComparatorLt(fatAndFlat, vertRect, rel);
+
+        assertComparatorLt(tallAndThin, smallSquare, rel);
+        assertComparatorLt(tallAndThin, horizRect, rel);
+        assertComparatorLt(tallAndThin, vertRect, rel);
+
+        assertComparatorLt(empty, largeSquare, rel);
+        assertComparatorLt(fatAndFlat, largeSquare, rel);
+        assertComparatorLt(tallAndThin, largeSquare, rel);
+        assertComparatorLt(smallSquare, largeSquare, rel);
+        assertComparatorLt(horizRect, largeSquare, rel);
+        assertComparatorLt(vertRect, largeSquare, rel);
+    }
+
+    private void assertOptimalSize(Size[] options, int minWidth, int minHeight, Size expected) {
+        Size verdict = CameraTooActivity.chooseBigEnoughSize(options, minWidth, minHeight);
+        assertEquals(String.format("Expected optimal size %s but got %s", expected, verdict),
+                verdict, expected);
+    }
+
+    @Test
+    public void chooseBigEnoughSize() {
+        Size empty = new Size(0, 0), fatAndFlat = new Size(100, 0), tallAndThin = new Size(0, 100);
+        Size smallSquare = new Size(4, 4), horizRect = new Size(8, 2), vertRect = new Size(2, 8);
+        Size largeSquare = new Size(5, 5);
+        Size[] siz =
+                { empty, fatAndFlat, tallAndThin, smallSquare, horizRect, vertRect, largeSquare };
+
+        assertOptimalSize(siz, 0, 0, empty);
+
+        assertOptimalSize(siz, 1, 0, fatAndFlat);
+        assertOptimalSize(siz, 0, 1, tallAndThin);
+
+        assertOptimalSize(siz, 4, 4, smallSquare);
+        assertOptimalSize(siz, 1, 1, smallSquare);
+        assertOptimalSize(siz, 2, 1, smallSquare);
+        assertOptimalSize(siz, 1, 2, smallSquare);
+        assertOptimalSize(siz, 3, 4, smallSquare);
+        assertOptimalSize(siz, 4, 3, smallSquare);
+
+        assertOptimalSize(siz, 8, 2, horizRect);
+        assertOptimalSize(siz, 5, 1, horizRect);
+        assertOptimalSize(siz, 5, 2, horizRect);
+
+        assertOptimalSize(siz, 2, 8, vertRect);
+        assertOptimalSize(siz, 1, 5, vertRect);
+        assertOptimalSize(siz, 2, 5, vertRect);
+
+        assertOptimalSize(siz, 5, 5, largeSquare);
+        assertOptimalSize(siz, 3, 5, largeSquare);
+        assertOptimalSize(siz, 5, 3, largeSquare);
+    }
+
+    private static final FilenameFilter OUTPUT_FILE_DECIDER = new FilenameFilter() {
+        @Override
+        public boolean accept(File dir, String filename) {
+            return filename.indexOf("cameratoo") == 0 &&
+                    filename.indexOf(".jpg") == filename.length() - ".jpg".length();
+        }};
+
+    private static <T> Set<T> newlyAddedElements(Set<T> before, Set<T> after) {
+        Set<T> result = new HashSet<T>(after);
+        result.removeAll(before);
+        return result;
+    }
+
+    @Test
+    public void capturedImageSaver() throws FileNotFoundException, IOException {
+        ByteBuffer buf = ByteBuffer.allocate(25);
+        for(int index = 0; index < buf.capacity(); ++index)
+            buf.put(index, (byte) index);
+
+        Image.Plane plane = mock(Image.Plane.class);
+        when(plane.getBuffer()).thenReturn(buf);
+        when(plane.getPixelStride()).thenReturn(1);
+        when(plane.getRowStride()).thenReturn(5);
+
+        Image.Plane[] onlyPlaneThatMatters = { plane };
+        Image image = mock(Image.class);
+        when(image.getPlanes()).thenReturn(onlyPlaneThatMatters);
+        when(image.getWidth()).thenReturn(5);
+        when(image.getHeight()).thenReturn(5);
+
+        File picturesFolder =
+                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
+        Set<File> preListing =
+                new HashSet<File>(Arrays.asList(picturesFolder.listFiles(OUTPUT_FILE_DECIDER)));
+
+        CameraTooActivity.CapturedImageSaver saver =
+                new CameraTooActivity.CapturedImageSaver(image);
+        saver.run();
+
+        Set<File> postListing =
+                new HashSet<File>(Arrays.asList(picturesFolder.listFiles(OUTPUT_FILE_DECIDER)));
+        Set<File> newFiles = newlyAddedElements(preListing, postListing);
+
+        assertEquals(newFiles.size(), 1);
+
+        File picture = newFiles.iterator().next();
+        FileInputStream istream = new FileInputStream(picture);
+
+        for(int count = 0; count < buf.capacity(); ++count) {
+            assertEquals(istream.read(), buf.get(count));
+        }
+        assertEquals(istream.read(), -1);
+        assertTrue(picture.delete());
+    }
+}
diff --git a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
index c7715ad..fc5426c 100644
--- a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
+++ b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
@@ -43,7 +43,6 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        HardwareRenderer.sUseRenderThread = true;
         setContentView(R.layout.activity_main);
         ListView lv = (ListView) findViewById(android.R.id.list);
         lv.setDrawSelectorOnTop(true);
diff --git a/tests/TtsTests/src/com/android/speech/tts/AbstractTtsSemioticClassTest.java b/tests/TtsTests/src/com/android/speech/tts/AbstractTtsSemioticClassTest.java
new file mode 100644
index 0000000..31484f4
--- /dev/null
+++ b/tests/TtsTests/src/com/android/speech/tts/AbstractTtsSemioticClassTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.speech.tts;
+
+import android.test.InstrumentationTestCase;
+
+import android.speech.tts.Markup;
+import android.speech.tts.Utterance;
+import android.speech.tts.Utterance.AbstractTtsSemioticClass;
+
+public class AbstractTtsSemioticClassTest extends InstrumentationTestCase {
+
+    public static class TtsMock extends AbstractTtsSemioticClass<TtsMock> {
+        public TtsMock() {
+            super();
+        }
+
+        public TtsMock(Markup markup) {
+            super();
+        }
+
+        public void setType(String type) {
+            mMarkup.setType(type);
+        }
+    }
+
+    public void testFluentAPI() {
+        new TtsMock()
+            .setPlainText("a plaintext") // from AbstractTts
+            .setGender(Utterance.GENDER_MALE) // from AbstractTtsSemioticClass
+            .setType("test"); // from TtsMock
+    }
+
+    public void testDefaultConstructor() {
+        new TtsMock();
+    }
+
+    public void testMarkupConstructor() {
+        Markup markup = new Markup();
+        new TtsMock(markup);
+    }
+
+    public void testGetType() {
+        TtsMock t = new TtsMock();
+        t.setType("type1");
+        assertEquals("type1", t.getType());
+        t.setType(null);
+        assertEquals(null, t.getType());
+        t.setType("type2");
+        assertEquals("type2", t.getType());
+    }
+
+
+    public void testDefaultGender() {
+        assertEquals(Utterance.GENDER_UNKNOWN, new TtsMock().getGender());
+    }
+
+    public void testSetGender() {
+        assertEquals(Utterance.GENDER_MALE,
+                     new TtsMock().setGender(Utterance.GENDER_MALE).getGender());
+    }
+
+    public void testSetGenderNegative() {
+        try {
+            new TtsMock().setGender(-1);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    public void testSetGenderOutOfBounds() {
+        try {
+            new TtsMock().setGender(4);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    public void testDefaultAnimacy() {
+        assertEquals(Utterance.ANIMACY_UNKNOWN, new TtsMock().getAnimacy());
+    }
+
+    public void testSetAnimacy() {
+        assertEquals(Utterance.ANIMACY_ANIMATE,
+                     new TtsMock().setAnimacy(Utterance.ANIMACY_ANIMATE).getAnimacy());
+    }
+
+    public void testSetAnimacyNegative() {
+        try {
+            new TtsMock().setAnimacy(-1);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    public void testSetAnimacyOutOfBounds() {
+        try {
+            new TtsMock().setAnimacy(4);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    public void testDefaultMultiplicity() {
+        assertEquals(Utterance.MULTIPLICITY_UNKNOWN, new TtsMock().getMultiplicity());
+    }
+
+    public void testSetMultiplicity() {
+        assertEquals(Utterance.MULTIPLICITY_DUAL,
+                     new TtsMock().setMultiplicity(Utterance.MULTIPLICITY_DUAL).getMultiplicity());
+    }
+
+    public void testSetMultiplicityNegative() {
+        try {
+            new TtsMock().setMultiplicity(-1);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    public void testSetMultiplicityOutOfBounds() {
+        try {
+            new TtsMock().setMultiplicity(4);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    public void testDefaultCase() {
+        assertEquals(Utterance.CASE_UNKNOWN, new TtsMock().getCase());
+    }
+
+    public void testSetCase() {
+        assertEquals(Utterance.CASE_VOCATIVE,
+                     new TtsMock().setCase(Utterance.CASE_VOCATIVE).getCase());
+    }
+
+    public void testSetCaseNegative() {
+        try {
+            new TtsMock().setCase(-1);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    public void testSetCaseOutOfBounds() {
+        try {
+            new TtsMock().setCase(9);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    public void testToString() {
+        TtsMock t = new TtsMock()
+            .setAnimacy(Utterance.ANIMACY_INANIMATE)
+            .setCase(Utterance.CASE_INSTRUMENTAL)
+            .setGender(Utterance.GENDER_FEMALE)
+            .setMultiplicity(Utterance.MULTIPLICITY_PLURAL);
+        String str =
+            "animacy: \"2\" " +
+            "case: \"8\" " +
+            "gender: \"3\" " +
+            "multiplicity: \"3\"";
+        assertEquals(str, t.toString());
+    }
+
+    public void testToStringSetToUnkown() {
+        TtsMock t = new TtsMock()
+            .setAnimacy(Utterance.ANIMACY_INANIMATE)
+            .setCase(Utterance.CASE_INSTRUMENTAL)
+            .setGender(Utterance.GENDER_FEMALE)
+            .setMultiplicity(Utterance.MULTIPLICITY_PLURAL)
+        // set back to unknown
+            .setAnimacy(Utterance.ANIMACY_UNKNOWN)
+            .setCase(Utterance.CASE_UNKNOWN)
+            .setGender(Utterance.GENDER_UNKNOWN)
+            .setMultiplicity(Utterance.MULTIPLICITY_UNKNOWN);
+        String str = "";
+        assertEquals(str, t.toString());
+    }
+
+}
diff --git a/tests/TtsTests/src/com/android/speech/tts/AbstractTtsTest.java b/tests/TtsTests/src/com/android/speech/tts/AbstractTtsTest.java
new file mode 100644
index 0000000..281c97f
--- /dev/null
+++ b/tests/TtsTests/src/com/android/speech/tts/AbstractTtsTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.speech.tts;
+
+import android.test.InstrumentationTestCase;
+
+import android.speech.tts.Markup;
+import android.speech.tts.Utterance.AbstractTts;
+
+public class AbstractTtsTest extends InstrumentationTestCase {
+
+    public static class TtsMock extends AbstractTts<TtsMock> {
+        public TtsMock() {
+            super();
+        }
+
+        public TtsMock(Markup markup) {
+            super();
+        }
+
+        public void setType(String type) {
+            mMarkup.setType(type);
+        }
+
+        @Override
+        public TtsMock setParameter(String key, String value) {
+           return super.setParameter(key, value);
+        }
+
+        @Override
+        public TtsMock removeParameter(String key) {
+           return super.removeParameter(key);
+        }
+    }
+
+    public void testDefaultConstructor() {
+        new TtsMock();
+    }
+
+    public void testMarkupConstructor() {
+        Markup markup = new Markup();
+        new TtsMock(markup);
+    }
+
+    public void testGetType() {
+        TtsMock t = new TtsMock();
+        t.setType("type1");
+        assertEquals("type1", t.getType());
+        t.setType(null);
+        assertEquals(null, t.getType());
+        t.setType("type2");
+        assertEquals("type2", t.getType());
+    }
+
+    public void testGeneratePlainText() {
+        assertNull(new TtsMock().generatePlainText());
+    }
+
+    public void testToString() {
+        TtsMock t = new TtsMock();
+        t.setType("a_type");
+        t.setPlainText("a plaintext");
+        t.setParameter("key1", "value1");
+        t.setParameter("aaa", "value2");
+        String str =
+            "type: \"a_type\" " +
+            "plain_text: \"a plaintext\" " +
+            "aaa: \"value2\" " +
+            "key1: \"value1\"";
+        assertEquals(str, t.toString());
+    }
+
+    public void testRemoveParameter() {
+        TtsMock t = new TtsMock();
+        t.setParameter("key1", "value 1");
+        t.setParameter("aaa", "value a");
+        t.removeParameter("key1");
+        String str =
+            "aaa: \"value a\"";
+        assertEquals(str, t.toString());
+    }
+
+    public void testRemoveParameterBySettingNull() {
+        TtsMock t = new TtsMock();
+        t.setParameter("key1", "value 1");
+        t.setParameter("aaa", "value a");
+        t.setParameter("aaa", null);
+        String str =
+            "key1: \"value 1\"";
+        assertEquals(str, t.toString());
+    }
+}
diff --git a/tests/TtsTests/src/com/android/speech/tts/MarkupTest.java b/tests/TtsTests/src/com/android/speech/tts/MarkupTest.java
new file mode 100644
index 0000000..7ef93ce
--- /dev/null
+++ b/tests/TtsTests/src/com/android/speech/tts/MarkupTest.java
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.speech.tts;
+
+import junit.framework.Assert;
+import android.os.Parcel;
+import android.test.InstrumentationTestCase;
+
+import android.speech.tts.Markup;
+
+public class MarkupTest extends InstrumentationTestCase {
+
+  public void testEmptyMarkup() {
+      Markup markup = new Markup();
+      assertNull(markup.getType());
+      assertNull(markup.getPlainText());
+      assertEquals(0, markup.parametersSize());
+      assertEquals(0, markup.nestedMarkupSize());
+  }
+
+  public void testGetSetType() {
+      Markup markup = new Markup();
+      markup.setType("one");
+      assertEquals("one", markup.getType());
+      markup.setType(null);
+      assertNull(markup.getType());
+      markup.setType("two");
+      assertEquals("two", markup.getType());
+  }
+
+  public void testGetSetPlainText() {
+      Markup markup = new Markup();
+      markup.setPlainText("one");
+      assertEquals("one", markup.getPlainText());
+      markup.setPlainText(null);
+      assertNull(markup.getPlainText());
+      markup.setPlainText("two");
+      assertEquals("two", markup.getPlainText());
+  }
+
+  public void testParametersSize1() {
+      Markup markup = new Markup();
+      markup.addNestedMarkup(new Markup());
+      assertEquals(1, markup.nestedMarkupSize());
+  }
+
+  public void testParametersSize2() {
+      Markup markup = new Markup();
+      markup.addNestedMarkup(new Markup());
+      markup.addNestedMarkup(new Markup());
+      assertEquals(2, markup.nestedMarkupSize());
+  }
+
+  public void testRemoveParameter() {
+      Markup m = new Markup("type");
+      m.setParameter("key1", "value1");
+      m.setParameter("key2", "value2");
+      m.setParameter("key3", "value3");
+      assertEquals(3, m.parametersSize());
+      m.removeParameter("key1");
+      assertEquals(2, m.parametersSize());
+      m.removeParameter("key3");
+      assertEquals(1, m.parametersSize());
+      assertNull(m.getParameter("key1"));
+      assertEquals("value2", m.getParameter("key2"));
+      assertNull(m.getParameter("key3"));
+  }
+
+  public void testEmptyEqual() {
+      Markup m1 = new Markup();
+      Markup m2 = new Markup();
+      assertTrue(m1.equals(m2));
+  }
+
+  public void testFilledEqual() {
+      Markup m1 = new Markup();
+      m1.setType("type");
+      m1.setPlainText("plain text");
+      m1.setParameter("key1", "value1");
+      m1.addNestedMarkup(new Markup());
+      Markup m2 = new Markup();
+      m2.setType("type");
+      m2.setPlainText("plain text");
+      m2.setParameter("key1", "value1");
+      m2.addNestedMarkup(new Markup());
+      assertTrue(m1.equals(m2));
+  }
+
+  public void testDifferentTypeEqual() {
+      Markup m1 = new Markup();
+      m1.setType("type1");
+      Markup m2 = new Markup();
+      m2.setType("type2");
+      assertFalse(m1.equals(m2));
+  }
+
+  public void testDifferentPlainTextEqual() {
+      Markup m1 = new Markup();
+      m1.setPlainText("plainText1");
+      Markup m2 = new Markup();
+      m2.setPlainText("plainText2");
+      assertFalse(m1.equals(m2));
+  }
+
+  public void testDifferentParamEqual() {
+      Markup m1 = new Markup();
+      m1.setParameter("test", "value1");
+      Markup m2 = new Markup();
+      m2.setParameter("test", "value2");
+      assertFalse(m1.equals(m2));
+  }
+
+  public void testDifferentParameterKeyEqual() {
+      Markup m1 = new Markup();
+      m1.setParameter("test1", "value");
+      Markup m2 = new Markup();
+      m2.setParameter("test2", "value");
+      assertFalse(m1.equals(m2));
+  }
+
+  public void testDifferentParameterValueEqual() {
+      Markup m1 = new Markup();
+      m1.setParameter("test", "value1");
+      Markup m2 = new Markup();
+      m2.setParameter("test", "value2");
+      assertFalse(m1.equals(m2));
+  }
+
+  public void testDifferentNestedMarkupEqual() {
+      Markup m1 = new Markup();
+      Markup nested = new Markup();
+      nested.setParameter("key", "value");
+      m1.addNestedMarkup(nested);
+      Markup m2 = new Markup();
+      m2.addNestedMarkup(new Markup());
+      assertFalse(m1.equals(m2));
+  }
+
+  public void testEmptyToFromString() {
+      Markup m1 = new Markup();
+      String str = m1.toString();
+      assertEquals("", str);
+
+      Markup m2 = Markup.markupFromString(str);
+      assertEquals(m1, m2);
+  }
+
+  public void testTypeToFromString() {
+      Markup m1 = new Markup("atype");
+      String str = m1.toString();
+      assertEquals("type: \"atype\"", str);
+      Markup m2 = Markup.markupFromString(str);
+      assertEquals(m1, m2);
+  }
+
+  public void testPlainTextToFromString() {
+      Markup m1 = new Markup();
+      m1.setPlainText("some_plainText");
+      String str = m1.toString();
+      assertEquals("plain_text: \"some_plainText\"", str);
+
+      Markup m2 = Markup.markupFromString(str);
+      assertEquals(m1, m2);
+  }
+
+  public void testParameterToFromString() {
+      Markup m1 = new Markup("cardinal");
+      m1.setParameter("integer", "-22");
+      String str = m1.toString();
+      assertEquals("type: \"cardinal\" integer: \"-22\"", str);
+      Markup m2 = Markup.markupFromString(str);
+      assertEquals(m1, m2);
+  }
+
+  // Parameters should be ordered alphabettically, so the output is stable.
+  public void testParameterOrderToFromString() {
+      Markup m1 = new Markup("cardinal");
+      m1.setParameter("ccc", "-");
+      m1.setParameter("aaa", "-");
+      m1.setParameter("aa", "-");
+      m1.setParameter("bbb", "-");
+      String str = m1.toString();
+      assertEquals(
+              "type: \"cardinal\" " +
+              "aa: \"-\" " +
+              "aaa: \"-\" " +
+              "bbb: \"-\" " +
+              "ccc: \"-\"",
+              str);
+      Markup m2 = Markup.markupFromString(str);
+      assertEquals(m1, m2);
+  }
+
+  public void testEmptyNestedToFromString() {
+      Markup m1 = new Markup("atype");
+      m1.addNestedMarkup(new Markup());
+      String str = m1.toString();
+      assertEquals("type: \"atype\" markup {}", str);
+      Markup m2 = Markup.markupFromString(str);
+      assertEquals(m1, m2);
+  }
+
+  public void testNestedWithTypeToFromString() {
+      Markup m1 = new Markup("atype");
+      m1.addNestedMarkup(new Markup("nested_type"));
+      String str = m1.toString();
+      assertEquals(
+              "type: \"atype\" " +
+              "markup { type: \"nested_type\" }",
+              str);
+      Markup m2 = Markup.markupFromString(str);
+      assertEquals(m1, m2);
+  }
+
+  public void testRemoveNestedMarkup() {
+      Markup m = new Markup("atype");
+      Markup m1 = new Markup("nested_type1");
+      Markup m2 = new Markup("nested_type2");
+      Markup m3 = new Markup("nested_type3");
+      m.addNestedMarkup(m1);
+      m.addNestedMarkup(m2);
+      m.addNestedMarkup(m3);
+      m.removeNestedMarkup(m1);
+      m.removeNestedMarkup(m3);
+      String str = m.toString();
+      assertEquals(
+              "type: \"atype\" " +
+              "markup { type: \"nested_type2\" }",
+              str);
+      Markup mFromString = Markup.markupFromString(str);
+      assertEquals(m, mFromString);
+  }
+
+  public void testLotsofNestingToFromString() {
+      Markup m1 = new Markup("top")
+          .addNestedMarkup(new Markup("top_child1")
+              .addNestedMarkup(new Markup("top_child1_child1"))
+              .addNestedMarkup(new Markup("top_child1_child2")))
+          .addNestedMarkup(new Markup("top_child2")
+              .addNestedMarkup(new Markup("top_child2_child2"))
+              .addNestedMarkup(new Markup("top_child2_child2")));
+
+      String str = m1.toString();
+      assertEquals(
+              "type: \"top\" " +
+              "markup { " +
+                  "type: \"top_child1\" " +
+                  "markup { type: \"top_child1_child1\" } " +
+                  "markup { type: \"top_child1_child2\" } " +
+              "} " +
+              "markup { " +
+                  "type: \"top_child2\" " +
+                  "markup { type: \"top_child2_child2\" } " +
+                  "markup { type: \"top_child2_child2\" } " +
+              "}",
+              str);
+      Markup m2 = Markup.markupFromString(str);
+      assertEquals(m1, m2);
+  }
+
+  public void testFilledToFromString() {
+      Markup m1 = new Markup("measure");
+      m1.setPlainText("fifty-five amps");
+      m1.setParameter("unit", "meter");
+      m1.addNestedMarkup(new Markup("cardinal").setParameter("integer", "55"));
+      String str = m1.toString();
+      assertEquals(
+              "type: \"measure\" " +
+              "plain_text: \"fifty-five amps\" " +
+              "unit: \"meter\" " +
+              "markup { type: \"cardinal\" integer: \"55\" }",
+              str);
+
+      Markup m2 = Markup.markupFromString(str);
+      assertEquals(m1, m2);
+  }
+
+  public void testErrorFromString() {
+      String str = "type: \"atype\" markup {mistake}";
+      try {
+          Markup.markupFromString(str);
+          Assert.fail("Expected IllegalArgumentException");
+      } catch (IllegalArgumentException e) {}
+  }
+
+  public void testEscapeQuotes() {
+      Markup m1 = new Markup("text")
+              .setParameter("something_unknown", "\"this\" is \"a sentence \" with quotes\"");
+      String str = m1.toString();
+      assertEquals(
+              "type: \"text\" " +
+              "something_unknown: \"\\\"this\\\" is \\\"a sentence \\\" with quotes\\\"\"",
+              str);
+
+      Markup m2 = Markup.markupFromString(str);
+      assertEquals(m1.toString(), m2.toString());
+      assertEquals(m1, m2);
+  }
+
+  public void testEscapeSlashes1() {
+      Markup m1 = new Markup("text")
+              .setParameter("something_unknown", "\\ \\\\ \t \n \"");
+      String str = m1.toString();
+      assertEquals(
+              "type: \"text\" " +
+              "something_unknown: \"\\\\ \\\\\\\\ \t \n \\\"\"",
+              str);
+
+      Markup m2 = Markup.markupFromString(str);
+      assertEquals(m1.toString(), m2.toString());
+      assertEquals(m1, m2);
+  }
+
+  public void testEscapeSlashes2() {
+      Markup m1 = new Markup("text")
+              .setParameter("something_unknown", "\\\"\\\"\\\\\"\"\\\\\\\"\"\"");
+      String str = m1.toString();
+      assertEquals(
+              "type: \"text\" " +
+              "something_unknown: \"\\\\\\\"\\\\\\\"\\\\\\\\\\\"\\\"\\\\\\\\\\\\\\\"\\\"\\\"\"",
+              str);
+
+      Markup m2 = Markup.markupFromString(str);
+      assertEquals(m1.toString(), m2.toString());
+      assertEquals(m1, m2);
+  }
+
+  public void testBadInput1() {
+      String str = "type: \"text\" text: \"\\\"";
+      try {
+          Markup.markupFromString(str);
+          fail("Expected IllegalArgumentException");
+      } catch (IllegalArgumentException e) {}
+  }
+
+  public void testBadInput2() {
+      String str = "type: \"text\" text: \"\\a\"";
+      try {
+          Markup.markupFromString(str);
+          fail("Expected IllegalArgumentException");
+      } catch (IllegalArgumentException e) {}
+  }
+
+  public void testValidParameterKey() {
+      Markup m = new Markup();
+      m.setParameter("ke9__yk_88ey_za7_", "test");
+  }
+
+  public void testInValidParameterKeyEmpty() {
+      Markup m = new Markup();
+      try {
+          m.setParameter("", "test");
+          fail("Expected IllegalArgumentException");
+      } catch (IllegalArgumentException e) {}
+  }
+
+  public void testInValidParameterKeyDollar() {
+      Markup m = new Markup();
+      try {
+          m.setParameter("ke9y$k88ey7", "test");
+          fail("Expected IllegalArgumentException");
+      } catch (IllegalArgumentException e) {}
+  }
+
+  public void testInValidParameterKeySpace() {
+      Markup m = new Markup();
+      try {
+          m.setParameter("ke9yk88ey7 ", "test");
+          fail("Expected IllegalArgumentException");
+      } catch (IllegalArgumentException e) {}
+  }
+
+  public void testValidType() {
+      new Markup("_this_is_1_valid_type_222");
+  }
+
+  public void testInValidTypeAmpersand() {
+      try {
+          new Markup("abcde1234&");
+          fail("Expected IllegalArgumentException");
+      } catch (IllegalArgumentException e) {}
+  }
+
+  public void testInValidTypeSpace() {
+      try {
+          new Markup(" ");
+          fail("Expected IllegalArgumentException");
+      } catch (IllegalArgumentException e) {}
+  }
+
+  public void testSimpleParcelable() {
+      Markup markup = new Markup();
+
+      Parcel parcel = Parcel.obtain();
+      markup.writeToParcel(parcel, 0);
+      parcel.setDataPosition(0);
+
+      Markup fromParcel = (Markup) Markup.CREATOR.createFromParcel(parcel);
+
+      assertFalse(markup == fromParcel);
+      assertEquals(markup, fromParcel);
+  }
+
+  public void testTypeParcelable() {
+      Markup markup = new Markup("text");
+
+      Parcel parcel = Parcel.obtain();
+      markup.writeToParcel(parcel, 0);
+      parcel.setDataPosition(0);
+
+      Markup fromParcel = (Markup) Markup.CREATOR.createFromParcel(parcel);
+
+      assertFalse(markup == fromParcel);
+      assertEquals(markup, fromParcel);
+  }
+
+  public void testPlainTextsParcelable() {
+      Markup markup = new Markup();
+      markup.setPlainText("plainText");
+
+      Parcel parcel = Parcel.obtain();
+      markup.writeToParcel(parcel, 0);
+      parcel.setDataPosition(0);
+
+      Markup fromParcel = (Markup) Markup.CREATOR.createFromParcel(parcel);
+
+      assertFalse(markup == fromParcel);
+      assertEquals(markup, fromParcel);
+  }
+
+  public void testParametersParcelable() {
+      Markup markup = new Markup();
+      markup.setParameter("key1", "value1");
+      markup.setParameter("key2", "value2");
+      markup.setParameter("key3", "value3");
+
+      Parcel parcel = Parcel.obtain();
+      markup.writeToParcel(parcel, 0);
+      parcel.setDataPosition(0);
+
+      Markup fromParcel = (Markup) Markup.CREATOR.createFromParcel(parcel);
+
+      assertFalse(markup == fromParcel);
+      assertEquals(markup, fromParcel);
+  }
+
+  public void testNestedParcelable() {
+      Markup markup = new Markup();
+      markup.addNestedMarkup(new Markup("first"));
+      markup.addNestedMarkup(new Markup("second"));
+      markup.addNestedMarkup(new Markup("third"));
+
+      Parcel parcel = Parcel.obtain();
+      markup.writeToParcel(parcel, 0);
+      parcel.setDataPosition(0);
+
+      Markup fromParcel = (Markup) Markup.CREATOR.createFromParcel(parcel);
+
+      assertFalse(markup == fromParcel);
+      assertEquals(markup, fromParcel);
+  }
+
+  public void testAllFieldsParcelable() {
+      Markup markup = new Markup("text");
+      markup.setPlainText("plain text");
+      markup.setParameter("key1", "value1");
+      markup.setParameter("key2", "value2");
+      markup.setParameter("key3", "value3");
+      markup.addNestedMarkup(new Markup("first"));
+      markup.addNestedMarkup(new Markup("second"));
+      markup.addNestedMarkup(new Markup("third"));
+
+      Parcel parcel = Parcel.obtain();
+      markup.writeToParcel(parcel, 0);
+      parcel.setDataPosition(0);
+
+      Markup fromParcel = (Markup) Markup.CREATOR.createFromParcel(parcel);
+
+      assertFalse(markup == fromParcel);
+      assertEquals(markup, fromParcel);
+  }
+
+  public void testKeyCannotBeType() {
+      try {
+          new Markup().setParameter("type", "vale");
+          fail("Expected IllegalArgumentException");
+      } catch (IllegalArgumentException e) {}
+  }
+
+  public void testKeyCannotBePlainText() {
+      try {
+          new Markup().setParameter("plain_text", "value");
+          fail("Expected IllegalArgumentException");
+      } catch (IllegalArgumentException e) {}
+  }
+}
diff --git a/tests/TtsTests/src/com/android/speech/tts/TtsCardinalTest.java b/tests/TtsTests/src/com/android/speech/tts/TtsCardinalTest.java
new file mode 100644
index 0000000..c34f4ac
--- /dev/null
+++ b/tests/TtsTests/src/com/android/speech/tts/TtsCardinalTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.speech.tts;
+
+import junit.framework.Assert;
+import android.test.InstrumentationTestCase;
+import android.test.MoreAsserts;
+
+import android.speech.tts.Markup;
+import android.speech.tts.Utterance;
+import android.speech.tts.Utterance.TtsCardinal;
+import android.speech.tts.Utterance.TtsText;
+
+public class TtsCardinalTest extends InstrumentationTestCase {
+
+    public void testConstruct() {
+        assertNotNull(new TtsCardinal(0));
+    }
+
+    public void testFluentAPI() {
+        new TtsCardinal()
+            .setPlainText("a plaintext") // from AbstractTts
+            .setGender(Utterance.GENDER_MALE) // from AbstractTtsSemioticClass
+            .setInteger("-10001"); // from TtsText
+    }
+
+    public void testZero() {
+        assertEquals("0", new TtsCardinal(0).getInteger());
+    }
+
+    public void testThirtyOne() {
+        assertEquals("31", new TtsCardinal(31).getInteger());
+    }
+
+    public void testMarkupZero() {
+        TtsCardinal c = new TtsCardinal(0);
+        Markup m = c.getMarkup();
+        assertEquals("0", m.getParameter("integer"));
+    }
+
+    public void testMarkupThirtyOne() {
+        TtsCardinal c = new TtsCardinal(31);
+        Markup m = c.getMarkup();
+        assertEquals("31", m.getParameter("integer"));
+    }
+
+    public void testMarkupThirtyOneString() {
+        TtsCardinal c = new TtsCardinal("31");
+        Markup m = c.getMarkup();
+        assertEquals("31", m.getParameter("integer"));
+    }
+
+    public void testMarkupNegativeThirtyOne() {
+        TtsCardinal c = new TtsCardinal(-31);
+        Markup m = c.getMarkup();
+        assertEquals("-31", m.getParameter("integer"));
+    }
+
+    public void testMarkupMinusZero() {
+        TtsCardinal c = new TtsCardinal("-0");
+        Markup m = c.getMarkup();
+        assertEquals("-0", m.getParameter("integer"));
+    }
+
+    public void testMarkupNegativeThirtyOneString() {
+        TtsCardinal c = new TtsCardinal("-31");
+        Markup m = c.getMarkup();
+        assertEquals("-31", m.getParameter("integer"));
+    }
+
+    public void testOnlyLetters() {
+        try {
+            new TtsCardinal("abc");
+            Assert.fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    public void testOnlyMinus() {
+        try {
+            new TtsCardinal("-");
+            Assert.fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    public void testNegativeLetters() {
+        try {
+            new TtsCardinal("-abc");
+            Assert.fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    public void testLetterNumberMix() {
+        try {
+            new TtsCardinal("-0a1b2c");
+            Assert.fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    public void letterNumberMix2() {
+        try {
+            new TtsCardinal("-a0b1c2");
+            Assert.fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {}
+    }
+}
diff --git a/tests/TtsTests/src/com/android/speech/tts/TtsTextTest.java b/tests/TtsTests/src/com/android/speech/tts/TtsTextTest.java
new file mode 100644
index 0000000..35fd453
--- /dev/null
+++ b/tests/TtsTests/src/com/android/speech/tts/TtsTextTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.speech.tts;
+
+import android.test.InstrumentationTestCase;
+
+import android.speech.tts.Markup;
+import android.speech.tts.Utterance;
+import android.speech.tts.Utterance.TtsText;
+
+public class TtsTextTest extends InstrumentationTestCase {
+
+    public void testConstruct() {
+        assertNotNull(new TtsText());
+    }
+
+    public void testFluentAPI() {
+        new TtsText()
+            .setPlainText("a plaintext") // from AbstractTts
+            .setGender(Utterance.GENDER_MALE) // from AbstractTtsSemioticClass
+            .setText("text"); // from TtsText
+    }
+
+    public void testConstructEmptyString() {
+        assertTrue(new TtsText("").getText().isEmpty());
+    }
+
+    public void testConstructString() {
+        assertEquals("this is a test.", new TtsText("this is a test.").getText());
+    }
+
+    public void testSetText() {
+        assertEquals("This is a test.", new TtsText().setText("This is a test.").getText());
+    }
+
+    public void testEmptyMarkup() {
+        TtsText t = new TtsText();
+        Markup m = t.getMarkup();
+        assertEquals("text", m.getType());
+        assertNull(m.getPlainText());
+        assertEquals(0, m.nestedMarkupSize());
+    }
+
+    public void testConstructStringMarkup() {
+        TtsText t = new TtsText("test");
+        Markup m = t.getMarkup();
+        assertEquals("text", m.getType());
+        assertEquals("test", m.getParameter("text"));
+        assertEquals(0, m.nestedMarkupSize());
+    }
+
+    public void testSetStringMarkup() {
+        TtsText t = new TtsText();
+        t.setText("test");
+        Markup m = t.getMarkup();
+        assertEquals("text", m.getType());
+        assertEquals("test", m.getParameter("text"));
+        assertEquals(0, m.nestedMarkupSize());
+    }
+}
diff --git a/tests/TtsTests/src/com/android/speech/tts/UtteranceTest.java b/tests/TtsTests/src/com/android/speech/tts/UtteranceTest.java
new file mode 100644
index 0000000..8014dd1
--- /dev/null
+++ b/tests/TtsTests/src/com/android/speech/tts/UtteranceTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.speech.tts;
+
+import android.speech.tts.Markup;
+import android.speech.tts.Utterance;
+import android.speech.tts.Utterance.TtsCardinal;
+import android.speech.tts.Utterance.TtsText;
+
+import android.test.InstrumentationTestCase;
+
+public class UtteranceTest extends InstrumentationTestCase {
+
+    public void testEmptyUtterance() {
+        Utterance utt = new Utterance();
+        assertEquals(0, utt.size());
+    }
+
+    public void testSizeCardinal() {
+        Utterance utt = new Utterance()
+                .append(new TtsCardinal(42));
+        assertEquals(1, utt.size());
+    }
+
+    public void testSizeCardinalString() {
+        Utterance utt = new Utterance()
+                .append(new TtsCardinal(42))
+                .append(new TtsText("is the answer"));
+        assertEquals(2, utt.size());
+    }
+
+    public void testMarkupEmpty() {
+        Markup m = new Utterance().createMarkup();
+        assertEquals("utterance", m.getType());
+        assertEquals("", m.getPlainText());
+    }
+
+    public void testMarkupCardinal() {
+        Utterance utt = new Utterance()
+                .append(new TtsCardinal(42));
+        Markup markup = utt.createMarkup();
+        assertEquals("utterance", markup.getType());
+        assertEquals("42", markup.getPlainText());
+        assertEquals("42", markup.getNestedMarkup(0).getParameter("integer"));
+        assertEquals("42", markup.getNestedMarkup(0).getPlainText());
+    }
+
+    public void testMarkupCardinalString() {
+        Utterance utt = new Utterance()
+                .append(new TtsCardinal(42))
+                .append(new TtsText("is not just a number."));
+        Markup markup = utt.createMarkup();
+        assertEquals("utterance", markup.getType());
+        assertEquals("42 is not just a number.", markup.getPlainText());
+        assertEquals("cardinal", markup.getNestedMarkup(0).getType());
+        assertEquals("42", markup.getNestedMarkup(0).getParameter("integer"));
+        assertEquals("42", markup.getNestedMarkup(0).getPlainText());
+        assertEquals("text", markup.getNestedMarkup(1).getType());
+        assertEquals("is not just a number.", markup.getNestedMarkup(1).getParameter("text"));
+        assertEquals("is not just a number.", markup.getNestedMarkup(1).getPlainText());
+    }
+
+    public void testTextCardinalToFromString() {
+        Utterance utt = new Utterance()
+                .append(new TtsCardinal(55))
+                .append(new TtsText("this is a text."));
+        String str = utt.toString();
+        assertEquals(
+            "type: \"utterance\" " +
+            "markup { " +
+                "type: \"cardinal\" " +
+                "integer: \"55\" " +
+            "} " +
+            "markup { " +
+                "type: \"text\" " +
+                "text: \"this is a text.\" " +
+            "}"
+            , str);
+
+        Utterance utt_new = Utterance.utteranceFromString(str);
+        assertEquals(str, utt_new.toString());
+    }
+
+    public void testNotUtteranceFromString() {
+        String str =
+            "type: \"this_is_not_an_utterance\" " +
+            "markup { " +
+                "type: \"cardinal\" " +
+                "plain_text: \"55\" " +
+                "integer: \"55\" " +
+            "}";
+        try {
+            Utterance.utteranceFromString(str);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    public void testFromMarkup() {
+        String markup_str =
+            "type: \"utterance\" " +
+            "markup { " +
+                "type: \"cardinal\" " +
+                "plain_text: \"55\" " +
+                "integer: \"55\" " +
+            "} " +
+            "markup { " +
+                "type: \"text\" " +
+                "plain_text: \"this is a text.\" " +
+                "text: \"this is a text.\" " +
+            "}";
+        Utterance utt = Utterance.utteranceFromString(markup_str);
+        assertEquals(markup_str, utt.toString());
+    }
+
+    public void testsetPlainText() {
+        Utterance utt = new Utterance()
+            .append(new TtsCardinal(-100).setPlainText("minus one hundred"));
+        assertEquals("minus one hundred", utt.get(0).getPlainText());
+    }
+
+    public void testRemoveTextThroughSet() {
+        Utterance utt = new Utterance()
+            .append(new TtsText().setText("test").setText(null));
+        assertNull(((TtsText) utt.get(0)).getText());
+    }
+
+    public void testUnknownNodeWithPlainText() {
+        String str =
+            "type: \"utterance\" " +
+            "markup { " +
+                "type: \"some_future_feature\" " +
+                "plain_text: \"biep bob bob\" " +
+                "bombom: \"lorum ipsum\" " +
+            "}";
+        Utterance utt = Utterance.utteranceFromString(str);
+        assertNotNull(utt);
+        assertEquals("text", utt.get(0).getType());
+        assertEquals("biep bob bob", ((TtsText) utt.get(0)).getText());
+    }
+
+    public void testUnknownNodeWithNoPlainTexts() {
+        String str =
+            "type: \"utterance\" " +
+            "markup { " +
+                "type: \"some_future_feature\" " +
+                "bombom: \"lorum ipsum\" " +
+                "markup { type: \"cardinal\" integer: \"10\" } " +
+                "markup { type: \"text\" text: \"pears\" } " +
+            "}";
+        Utterance utt = Utterance.utteranceFromString(str);
+        assertEquals(
+            "type: \"utterance\" " +
+            "markup { type: \"cardinal\" integer: \"10\" } " +
+            "markup { type: \"text\" text: \"pears\" }", utt.toString());
+    }
+
+    public void testCreateWarningOnFallbackTrue() {
+        Utterance utt = new Utterance()
+          .append(new TtsText("test"))
+          .setNoWarningOnFallback(true);
+        assertEquals(
+            "type: \"utterance\" " +
+            "no_warning_on_fallback: \"true\" " +
+            "markup { " +
+                "type: \"text\" " +
+                "text: \"test\" " +
+            "}", utt.toString());
+    }
+
+    public void testCreateWarningOnFallbackFalse() {
+        Utterance utt = new Utterance()
+          .append(new TtsText("test"))
+          .setNoWarningOnFallback(false);
+        assertEquals(
+            "type: \"utterance\" " +
+            "no_warning_on_fallback: \"false\" " +
+            "markup { " +
+                "type: \"text\" " +
+                "text: \"test\" " +
+            "}", utt.toString());
+    }
+
+    public void testCreatePlainTexts() {
+        Utterance utt = new Utterance()
+            .append(new TtsText("test"))
+            .append(new TtsCardinal(-55));
+        assertEquals(
+            "type: \"utterance\" " +
+            "plain_text: \"test -55\" " +
+            "markup { type: \"text\" plain_text: \"test\" text: \"test\" } " +
+            "markup { type: \"cardinal\" plain_text: \"-55\" integer: \"-55\" }",
+            utt.createMarkup().toString()
+        );
+    }
+
+    public void testDontOverwritePlainTexts() {
+        Utterance utt = new Utterance()
+            .append(new TtsText("test").setPlainText("else"))
+            .append(new TtsCardinal(-55).setPlainText("44"));
+        assertEquals(
+            "type: \"utterance\" " +
+            "plain_text: \"else 44\" " +
+            "markup { type: \"text\" plain_text: \"else\" text: \"test\" } " +
+            "markup { type: \"cardinal\" plain_text: \"44\" integer: \"-55\" }",
+            utt.createMarkup().toString()
+        );
+    }
+
+    public void test99BottlesOnWallMarkup() {
+        Utterance utt = new Utterance()
+            .append("there are")
+            .append(99)
+            .append("bottles on the wall.");
+        assertEquals(
+                "type: \"utterance\" " +
+                "plain_text: \"there are 99 bottles on the wall.\" " +
+                "markup { type: \"text\" plain_text: \"there are\" text: \"there are\" } " +
+                "markup { type: \"cardinal\" plain_text: \"99\" integer: \"99\" } " +
+                "markup { type: \"text\" plain_text: \"bottles on the wall.\" text: \"bottles on the wall.\" }",
+                utt.createMarkup().toString());
+        assertEquals("99", utt.createMarkup().getNestedMarkup(1).getPlainText());
+        Markup markup = new Markup(utt.createMarkup());
+        assertEquals("99", markup.getNestedMarkup(1).getPlainText());
+    }
+
+    public void testWhat() {
+        Utterance utt = new Utterance()
+            .append("there are")
+            .append(99)
+            .append("bottles on the wall.");
+        Markup m = utt.createMarkup();
+        m.getNestedMarkup(1).getPlainText().equals("99");
+    }
+}
diff --git a/tests/VectorDrawableTest/AndroidManifest.xml b/tests/VectorDrawableTest/AndroidManifest.xml
index 28c5f33..db2efc3 100644
--- a/tests/VectorDrawableTest/AndroidManifest.xml
+++ b/tests/VectorDrawableTest/AndroidManifest.xml
@@ -17,12 +17,31 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.test.dynamic" >
+
     <uses-sdk android:minSdkVersion="20" />
 
     <application
         android:hardwareAccelerated="true"
         android:label="vector" >
         <activity
+            android:name="VectorDrawablePerformance"
+            android:label="Vector Performance" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="com.android.test.dynamic.TEST" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name="VectorDrawableAnimation"
+            android:label="VectorTestAnimation" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="com.android.test.dynamic.TEST" />
+            </intent-filter>
+        </activity>
+        <activity
             android:name="VectorDrawableTest"
             android:label="Vector Icon" >
             <intent-filter>
@@ -33,6 +52,15 @@
             </intent-filter>
         </activity>
         <activity
+            android:name="AnimatedVectorDrawableTest"
+            android:label="AnimatedVectorDrawableTest" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="com.android.test.dynamic.TEST" />
+            </intent-filter>
+        </activity>
+        <activity
             android:name="VectorDrawable01"
             android:label="VectorTest1" >
             <intent-filter>
@@ -41,19 +69,7 @@
                 <category android:name="com.android.test.dynamic.TEST" />
             </intent-filter>
         </activity>
-
-         <activity
-            android:name="VectorDrawablePerformance"
-            android:label="Vector Performance" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-
-                <category android:name="com.android.test.dynamic.TEST" />
-            </intent-filter>
-
-        </activity>
-
-         <activity
+        <activity
             android:name="VectorDrawableDupPerf"
             android:label="Vector Performance of clones" >
             <intent-filter>
@@ -61,7 +77,6 @@
 
                 <category android:name="com.android.test.dynamic.TEST" />
             </intent-filter>
-
         </activity>
         <activity
             android:name="VectorDrawableStaticPerf"
@@ -71,9 +86,7 @@
 
                 <category android:name="com.android.test.dynamic.TEST" />
             </intent-filter>
-
         </activity>
-
         <activity
             android:name="VectorCheckbox"
             android:label="On a Checkbox" >
@@ -82,7 +95,6 @@
 
                 <category android:name="com.android.test.dynamic.TEST" />
             </intent-filter>
-
         </activity>
         <activity
             android:name="VectorPathChecking"
@@ -92,7 +104,6 @@
 
                 <category android:name="com.android.test.dynamic.TEST" />
             </intent-filter>
-
         </activity>
     </application>
 
diff --git a/tests/VectorDrawableTest/res/anim/trim_path_animation01.xml b/tests/VectorDrawableTest/res/anim/trim_path_animation01.xml
new file mode 100644
index 0000000..d47e019
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/trim_path_animation01.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <set android:ordering="sequentially" >
+        <objectAnimator
+            android:duration="5000"
+            android:propertyName="trimPathEnd"
+            android:valueFrom="0"
+            android:valueTo="1"
+            android:valueType="floatType" />
+        <objectAnimator
+            android:duration="5000"
+            android:propertyName="trimPathEnd"
+            android:valueFrom="1"
+            android:valueTo="0"
+            android:valueType="floatType" />
+    </set>
+
+</set>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/anim/trim_path_animation02.xml b/tests/VectorDrawableTest/res/anim/trim_path_animation02.xml
new file mode 100644
index 0000000..3bf2865
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/trim_path_animation02.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <objectAnimator
+        android:duration="5000"
+        android:propertyName="fill"
+        android:valueFrom="#FF000000"
+        android:valueTo="#FFFF0000"/>
+
+</set>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/anim/trim_path_animation03.xml b/tests/VectorDrawableTest/res/anim/trim_path_animation03.xml
new file mode 100644
index 0000000..72beba2
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/trim_path_animation03.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <objectAnimator
+        android:duration="6000"
+        android:propertyName="rotation"
+        android:valueFrom="0"
+        android:valueTo="360"/>
+
+</set>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/anim/trim_path_animation04.xml b/tests/VectorDrawableTest/res/anim/trim_path_animation04.xml
new file mode 100644
index 0000000..ff86668
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/trim_path_animation04.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <objectAnimator
+        android:duration="9000"
+        android:propertyName="rotation"
+        android:valueFrom="0"
+        android:valueTo="360"/>
+
+</set>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/animation_drawable_vector.xml b/tests/VectorDrawableTest/res/drawable/animation_drawable_vector.xml
new file mode 100644
index 0000000..a588960
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/animation_drawable_vector.xml
@@ -0,0 +1,36 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
+     android:id="@+id/animation_drawable_vector" android:oneshot="false">
+    <item android:drawable="@drawable/vector_drawable01" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable02" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable03" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable04" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable05" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable06" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable07" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable08" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable09" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable10" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable11" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable12" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable13" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable14" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable15" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable16" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable17" android:duration="300" />
+    <item android:drawable="@drawable/vector_drawable18" android:duration="300" />
+ </animation-list>
diff --git a/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml
new file mode 100644
index 0000000..b8681b6
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml
@@ -0,0 +1,36 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:drawable="@drawable/vector_drawable12" >
+
+    <target
+        android:name="pie1"
+        android:animation="@anim/trim_path_animation01" />
+    <target
+        android:name="v"
+        android:animation="@anim/trim_path_animation02" />
+
+    <target
+        android:name="rotationGroup"
+        android:animation="@anim/trim_path_animation03" />
+    <target
+        android:name="rotationGroup3"
+        android:animation="@anim/trim_path_animation03" />
+    <target
+        android:name="rotationGroupBlue"
+        android:animation="@anim/trim_path_animation03" />
+
+</animated-vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable01.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable01.xml
index 118f258..66a9452 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable01.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable01.xml
@@ -13,8 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:versionCode="1" >
+<vector xmlns:android="http://schemas.android.com/apk/res/android">
 
     <size
         android:height="48dp"
@@ -24,12 +23,13 @@
         android:viewportHeight="480"
         android:viewportWidth="480" />
 
-    <path
-        android:name="box1"
-        android:fill="?android:attr/colorControlActivated"
-        android:pathData="m20,200l100,90l180,-180l-35,-35l-145,145l-60,-60l-40,40z"
-        android:stroke="?android:attr/colorControlActivated"
-        android:strokeLineCap="round"
-        android:strokeLineJoin="round" />
-
-</vector>
\ No newline at end of file
+    <group>
+        <path
+            android:name="box1"
+            android:pathData="m20,200l100,90l180,-180l-35,-35l-145,145l-60,-60l-40,40z"
+            android:fill="?android:attr/colorControlActivated"
+            android:stroke="?android:attr/colorControlActivated"
+            android:strokeLineCap="round"
+            android:strokeLineJoin="round" />
+    </group>
+</vector>
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable02.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable02.xml
index 034f7a0..40f23f0 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable02.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable02.xml
@@ -1,5 +1,4 @@
-<!--
- Copyright (C) 2014 The Android Open Source Project
+<!-- Copyright (C) 2014 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -16,23 +15,22 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android" >
 
     <size
-        android:height="64dp"
-        android:width="64dp" />
+        android:width="64dp"
+        android:height="64dp"/>
 
-    <viewport
-        android:viewportHeight="320"
-        android:viewportWidth="320" />
-
-    <path
-        android:name="house"
-        android:fill="#ff440000"
-        android:pathData="M 130,225 L 130,115 L 130,115 L 70,15 L 10,115 L 10,115 L 10,225 z"
-        android:pivotX="70"
-        android:pivotY="120"
+    <viewport android:viewportWidth="320"
+          android:viewportHeight="320"/>
+    <group
         android:rotation="180"
-        android:stroke="#FF00FF00"
-        android:strokeWidth="10"
-        android:trimPathEnd=".9"
-        android:trimPathStart=".1" />
-
-</vector>
\ No newline at end of file
+        android:pivotX="70"
+        android:pivotY="120">
+        <path
+            android:name="house"
+            android:pathData="M 130,225 L 130,115 L 130,115 L 70,15 L 10,115 L 10,115 L 10,225 z"
+            android:fill="#ff440000"
+            android:stroke="#FF00FF00"
+            android:strokeWidth="10"
+            android:trimPathStart=".1"
+            android:trimPathEnd=".9"/>
+    </group>
+</vector>
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable03.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable03.xml
index 451b28e..5b4c4ab 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable03.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable03.xml
@@ -23,40 +23,47 @@
         android:viewportHeight="12.25"
         android:viewportWidth="7.30625" />
 
-    <path
-        android:name="clip1"
-        android:clipToPath="true"
-        android:pathData="
-                M 0, 0
-                l 7.3, 0
-                l 0, 0
-                l -7.3, 0
-                z"
+    <group
         android:pivotX="3.65"
         android:pivotY="6.125"
-        android:rotation="-30" />
-    <path
-        android:name="one"
-        android:fill="#ff88ff"
-        android:pathData="M 1.215625,9.5l 1.9375,0.0 0.0,-6.671875 -2.109375,0.421875 0.0,-1.078125
-                l 2.09375,-0.421875 1.1874998,0.0 0.0,7.75 1.9375,0.0 0.0,1.0
-                l -5.046875,0.0 0.0,-1.0Z" />
-    <path
-        android:name="clip2"
-        android:clipToPath="true"
-        android:pathData="
-                M 0, 0
+        android:rotation="-30" >
+        <path
+            android:name="clip1"
+            android:clipToPath="true"
+            android:pathData="
+                M 0, 6.125
                 l 7.3, 0
                 l 0, 12.25
                 l -7.3, 0
-                z"
+                z" />
+    </group>
+    <group>
+        <path
+            android:name="one"
+            android:fill="#ff88ff"
+            android:pathData="M 1.215625,9.5l 1.9375,0.0 0.0,-6.671875 -2.109375,0.421875 0.0,-1.078125
+                l 2.09375,-0.421875 1.1874998,0.0 0.0,7.75 1.9375,0.0 0.0,1.0
+                l -5.046875,0.0 0.0,-1.0Z" />
+    </group>
+    <group
         android:pivotX="3.65"
         android:pivotY="6.125"
-        android:rotation="-30" />
-    <path
-        android:name="two"
-        android:fill="#ff88ff"
-        android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0 -5.5625,0.0 0.0,-1.0q 0.671875,-0.6875 1.828125,-1.859375
+        android:rotation="-30" >
+        <path
+            android:name="clip2"
+            android:clipToPath="true"
+            android:pathData="
+                M 0, 0
+                l 7.3, 0
+                l 0, 6.125
+                l -7.3, 0
+                z" />
+    </group>
+    <group>
+        <path
+            android:name="two"
+            android:fill="#ff88ff"
+            android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0 -5.5625,0.0 0.0,-1.0q 0.671875,-0.6875 1.828125,-1.859375
                         q 1.1718752,-1.1875 1.4687502,-1.53125 0.578125,-0.625 0.796875,-1.0625
                         q 0.234375,-0.453125 0.234375,-0.875 0.0,-0.703125 -0.5,-1.140625
                         q -0.484375,-0.4375 -1.2656252,-0.4375 -0.5625,0.0 -1.1875,0.1875
@@ -65,5 +72,6 @@
                         q 0.8125,0.671875 0.8125,1.8125 0.0,0.53125 -0.203125,1.015625
                         q -0.203125,0.484375 -0.734375,1.140625 -0.15625,0.171875 -0.9375,0.984375
                         q -0.78125024,0.8125 -2.2187502,2.265625Z" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable04.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable04.xml
index 6f9caa8..90694fb 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable04.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable04.xml
@@ -1,5 +1,4 @@
-<!--
- Copyright (C) 2014 The Android Open Source Project
+<!-- Copyright (C) 2014 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -13,44 +12,48 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+<vector xmlns:android="http://schemas.android.com/apk/res/android">
 
     <size
-        android:height="64dp"
-        android:width="64dp" />
+            android:width="64dp"
+            android:height="64dp"/>
 
     <viewport
-        android:viewportHeight="12.25"
-        android:viewportWidth="7.30625" />
+            android:viewportWidth="7.30625"
+            android:viewportHeight="12.25"/>
 
-    <path
-        android:name="clip1"
-        android:clipToPath="true"
-        android:fill="#112233"
-        android:pathData="
+    <group>
+        <path
+                android:name="clip1"
+                android:pathData="
                 M 3.65, 6.125
                 m -.001, 0
                 a .001,.001 0 1,0 .002,0
-                a .001,.001 0 1,0 -.002,0z" />
-    <path
-        android:name="one"
-        android:fill="#ff88ff"
-        android:pathData="M 1.215625,9.5l 1.9375,0.0 0.0,-6.671875 -2.109375,0.421875 0.0,-1.078125
+                a .001,.001 0 1,0 -.002,0z"
+                android:clipToPath="true"
+                android:fill="#112233"
+                />
+
+        <path
+                android:name="one"
+                android:pathData="M 1.215625,9.5l 1.9375,0.0 0.0,-6.671875 -2.109375,0.421875 0.0,-1.078125
                 l 2.09375,-0.421875 1.1874998,0.0 0.0,7.75 1.9375,0.0 0.0,1.0
-                l -5.046875,0.0 0.0,-1.0Z" />
-    <path
-        android:name="clip2"
-        android:clipToPath="true"
-        android:fill="#112233"
-        android:pathData="
+                l -5.046875,0.0 0.0,-1.0Z"
+                android:fill="#ff88ff"
+                />
+        <path
+                android:name="clip2"
+                android:pathData="
                 M 3.65, 6.125
                 m -6, 0
                 a 6,6 0 1,0 12,0
-                a 6,6 0 1,0 -12,0z" />
-    <path
-        android:name="two"
-        android:fill="#ff88ff"
-        android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0 -5.5625,0.0 0.0,-1.0q 0.671875,-0.6875 1.828125,-1.859375
+                a 6,6 0 1,0 -12,0z"
+                android:clipToPath="true"
+                android:fill="#112233"
+                />
+        <path
+                android:name="two"
+                android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0 -5.5625,0.0 0.0,-1.0q 0.671875,-0.6875 1.828125,-1.859375
                         q 1.1718752,-1.1875 1.4687502,-1.53125 0.578125,-0.625 0.796875,-1.0625
                         q 0.234375,-0.453125 0.234375,-0.875 0.0,-0.703125 -0.5,-1.140625
                         q -0.484375,-0.4375 -1.2656252,-0.4375 -0.5625,0.0 -1.1875,0.1875
@@ -58,6 +61,8 @@
                         q 0.625,-0.15625 1.140625,-0.15625 1.3593752,0.0 2.1718752,0.6875
                         q 0.8125,0.671875 0.8125,1.8125 0.0,0.53125 -0.203125,1.015625
                         q -0.203125,0.484375 -0.734375,1.140625 -0.15625,0.171875 -0.9375,0.984375
-                        q -0.78125024,0.8125 -2.2187502,2.265625Z" />
-
-</vector>
\ No newline at end of file
+                        q -0.78125024,0.8125 -2.2187502,2.265625Z"
+                android:fill="#ff88ff"
+                />
+    </group>
+</vector>
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable05.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable05.xml
index e6c2557..c6595fa 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable05.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable05.xml
@@ -23,17 +23,18 @@
         android:viewportHeight="12.25"
         android:viewportWidth="7.30625" />
 
-    <path
-        android:name="one"
-        android:fill="#ffff00"
-        android:pathData="M 1.215625,9.5l 1.9375,0.0 0.0,-6.671875 -2.109375,0.421875 0.0,-1.078125
+    <group>
+        <path
+            android:name="one"
+            android:fill="#ffff00"
+            android:pathData="M 1.215625,9.5l 1.9375,0.0 0.0,-6.671875 -2.109375,0.421875 0.0,-1.078125
                 l 2.09375,-0.421875 1.1874998,0.0 0.0,7.75 1.9375,0.0 0.0,1.0
                 l -5.046875,0.0 0.0,-1.0Z" />
-    <path
-        android:name="two"
-        android:fill="#ffff00"
-        android:fillOpacity="0"
-        android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0 -5.5625,0.0 0.0,-1.0q 0.671875,-0.6875 1.828125,-1.859375
+        <path
+            android:name="two"
+            android:fill="#ffff00"
+            android:fillOpacity="0"
+            android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0 -5.5625,0.0 0.0,-1.0q 0.671875,-0.6875 1.828125,-1.859375
                         q 1.1718752,-1.1875 1.4687502,-1.53125 0.578125,-0.625 0.796875,-1.0625
                         q 0.234375,-0.453125 0.234375,-0.875 0.0,-0.703125 -0.5,-1.140625
                         q -0.484375,-0.4375 -1.2656252,-0.4375 -0.5625,0.0 -1.1875,0.1875
@@ -42,5 +43,5 @@
                         q 0.8125,0.671875 0.8125,1.8125 0.0,0.53125 -0.203125,1.015625
                         q -0.203125,0.484375 -0.734375,1.140625 -0.15625,0.171875 -0.9375,0.984375
                         q -0.78125024,0.8125 -2.2187502,2.265625Z" />
-
+    </group>
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable06.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable06.xml
index 3f8cc09..ab5f7f4 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable06.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable06.xml
@@ -1,5 +1,4 @@
-<!--
- Copyright (C) 2014 The Android Open Source Project
+<!-- Copyright (C) 2014 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -16,38 +15,38 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android" >
 
     <size
-        android:height="64dp"
-        android:width="64dp" />
+            android:width="64dp"
+            android:height="64dp"/>
 
     <viewport
-        android:viewportHeight="700"
-        android:viewportWidth="700" />
+            android:viewportWidth="700"
+            android:viewportHeight="700"/>
 
-    <path
-        android:name="path2451"
-        android:pathData="M 569.374 461.472L 569.374 160.658L 160.658 160.658L 160.658 461.472L 569.374 461.472z"
-        android:stroke="#FF000000"
-        android:strokeWidth="30.65500000000000" />
-    <path
-        android:name="path2453"
-        android:pathData="M 365.015 311.066"
-        android:stroke="#FF000000"
-        android:strokeWidth="30.655000000000001" />
-    <path
-        android:name="path2455"
-        android:fill="#FFFFFFFF"
-        android:pathData="M 164.46 164.49L 340.78 343.158C 353.849 356.328 377.63 356.172 390.423 343.278L 566.622 165.928"
-        android:stroke="#FF000000"
-        android:strokeWidth="30.655000000000001" />
-    <path
-        android:name="path2457"
-        android:pathData="M 170.515 451.566L 305.61 313.46"
-        android:stroke="#000000"
-        android:strokeWidth="30.655000000000001" />
-    <path
-        android:name="path2459"
-        android:pathData="M 557.968 449.974L 426.515 315.375"
-        android:stroke="#000000"
-        android:strokeWidth="30.655000000000001" />
-
-</vector>
\ No newline at end of file
+    <group>
+        <path android:pathData="M 569.374 461.472L 569.374 160.658L 160.658 160.658L 160.658 461.472L 569.374 461.472z"
+              android:name="path2451"
+              android:fill="#00000000"
+              android:stroke="#FF000000"
+              android:strokeWidth="30.65500000000000"/>
+        <path android:pathData="M 365.015 311.066"
+              android:name="path2453"
+              android:fill="#00000000"
+              android:stroke="#FF000000"
+              android:strokeWidth="30.655000000000001"/>
+        <path android:pathData="M 164.46 164.49L 340.78 343.158C 353.849 356.328 377.63 356.172 390.423 343.278L 566.622 165.928"
+              android:name="path2455"
+              android:stroke="#FF000000"
+              android:fill="#FFFFFFFF"
+              android:strokeWidth="30.655000000000001"/>
+        <path android:pathData="M 170.515 451.566L 305.61 313.46"
+              android:name="path2457"
+              android:fill="#00000000"
+              android:stroke="#000000"
+              android:strokeWidth="30.655000000000001"/>
+        <path android:pathData="M 557.968 449.974L 426.515 315.375"
+              android:name="path2459"
+              android:fill="#00000000"
+              android:stroke="#000000"
+              android:strokeWidth="30.655000000000001"/>
+    </group>
+</vector>
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable07.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable07.xml
index 4db5090..7c7e679 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable07.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable07.xml
@@ -1,5 +1,4 @@
-<!--
- Copyright (C) 2014 The Android Open Source Project
+<!-- Copyright (C) 2014 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -13,21 +12,21 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
-
+<vector xmlns:android="http://schemas.android.com/apk/res/android"   >
     <size
-        android:height="64dp"
-        android:width="64dp" />
+            android:width="64dp"
+            android:height="64dp"/>
 
-    <viewport
-        android:viewportHeight="110"
-        android:viewportWidth="140" />
+    <viewport android:viewportWidth="140"
+          android:viewportHeight="110"/>
 
-    <path
-        android:name="back"
-        android:fill="#ffffffff"
-        android:pathData="M 20,55 l 35.3,-35.3 7.07,7.07 -35.3,35.3 z
+    <group>
+        <path
+                android:name="back"
+                android:pathData="M 20,55 l 35.3,-35.3 7.07,7.07 -35.3,35.3 z
               M 27,50 l 97,0 0,10 -97,0 z
-              M 20,55 l 7.07,-7.07 35.3,35.3 -7.07,7.07 z" />
-
-</vector>
\ No newline at end of file
+              M 20,55 l 7.07,-7.07 35.3,35.3 -7.07,7.07 z"
+                android:fill="#ffffffff"
+                />
+    </group>
+</vector>
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable08.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable08.xml
index 44ef979..59f7459 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable08.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable08.xml
@@ -1,5 +1,4 @@
-<!--
- Copyright (C) 2014 The Android Open Source Project
+<!-- Copyright (C) 2014 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -16,18 +15,20 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android" >
 
     <size
-        android:height="64dp"
-        android:width="64dp" />
+            android:width="64dp"
+            android:height="64dp"/>
 
-    <viewport
-        android:viewportHeight="600"
-        android:viewportWidth="600" />
 
-    <path
-        android:name="pie1"
-        android:fill="#ffffcc00"
-        android:pathData="M535.441,412.339A280.868,280.868 0 1,1 536.186,161.733L284.493,286.29Z"
-        android:stroke="#FF00FF00"
-        android:strokeWidth="1" />
+    <viewport android:viewportWidth="600"
+          android:viewportHeight="600"/>
 
-</vector>
\ No newline at end of file
+    <group>
+        <path
+                android:name="pie1"
+                android:pathData="M535.441,412.339A280.868,280.868 0 1,1 536.186,161.733L284.493,286.29Z"
+                android:fill="#ffffcc00"
+                android:stroke="#FF00FF00"
+                android:strokeWidth="1"/>
+    </group>
+
+</vector>
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable09.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable09.xml
index 248a143..c93c85f 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable09.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable09.xml
@@ -23,12 +23,14 @@
         android:viewportHeight="200"
         android:viewportWidth="200" />
 
-    <path
-        android:name="house"
-        android:fill="#ffffffff"
-        android:pathData="M 100,20 l 0,0 0,140 -80,0 z M 100,20 l 0,0 80,140 -80,0 z"
+    <group
         android:pivotX="100"
         android:pivotY="100"
-        android:rotation="90" />
+        android:rotation="90">
+        <path
+            android:name="house"
+            android:fill="#ffffffff"
+            android:pathData="M 100,20 l 0,0 0,140 -80,0 z M 100,20 l 0,0 80,140 -80,0 z"/>
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable10.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable10.xml
index 56c2972..8484e9e 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable10.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable10.xml
@@ -21,24 +21,26 @@
         android:width="64dp" />
 
     <viewport
-        android:viewportHeight="200"
-        android:viewportWidth="200" />
+        android:viewportWidth="200"
+        android:viewportHeight="200"/>
 
-    <path
-        android:name="bar3"
-        android:fill="#FFFFFFFF"
-        android:pathData="M49.001,60c-5.466,0 -9.899,4.478 -9.899,10s4.434,10,9.899,10c5.468,0,9.899 -4.478,9.899 -10S54.469,60,49.001,60z" />
-    <path
-        android:name="bar2"
-        android:fill="#FFFFFFFF"
-        android:pathData="M28.001,48.787l7,7.07c7.731 -7.811,20.269 -7.81,28.001,0l6.999 -7.07C58.403,37.071,39.599,37.071,28.001,48.787z" />
-    <path
-        android:name="bar1"
-        android:fill="#FF555555"
-        android:pathData="M14.001,34.645   L21,41.716c15.464 -15.621,40.536 -15.621,56,0l7.001 -7.071C64.672,15.119,33.33,15.119,14.001,34.645z" />
-    <path
-        android:name="bar0"
-        android:fill="#FF555555"
-        android:pathData="M0,20.502l6.999,7.071   c23.196 -23.431,60.806 -23.431,84.002,0L98,20.503C70.938 -6.834,27.063 -6.834,0,20.502z" />
+    <group>
+        <path
+            android:name="bar3"
+            android:fill="#FFFFFFFF"
+            android:pathData="M49.001,60c-5.466,0 -9.899,4.478 -9.899,10s4.434,10,9.899,10c5.468,0,9.899 -4.478,9.899 -10S54.469,60,49.001,60z" />
+        <path
+            android:name="bar2"
+            android:fill="#FFFFFFFF"
+            android:pathData="M28.001,48.787l7,7.07c7.731 -7.811,20.269 -7.81,28.001,0l6.999 -7.07C58.403,37.071,39.599,37.071,28.001,48.787z" />
+        <path
+            android:name="bar1"
+            android:fill="#FF555555"
+            android:pathData="M14.001,34.645   L21,41.716c15.464 -15.621,40.536 -15.621,56,0l7.001 -7.071C64.672,15.119,33.33,15.119,14.001,34.645z" />
+        <path
+            android:name="bar0"
+            android:fill="#FF555555"
+            android:pathData="M0,20.502l6.999,7.071   c23.196 -23.431,60.806 -23.431,84.002,0L98,20.503C70.938 -6.834,27.063 -6.834,0,20.502z" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable11.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable11.xml
index 16d8b48..3422bbf 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable11.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable11.xml
@@ -23,16 +23,17 @@
         android:viewportHeight="80"
         android:viewportWidth="40" />
 
-    <path
-        android:name="battery"
-        android:fill="#3388ff"
-        android:pathData="M 20.28125,2.0000002 C 17.352748,2.0000002 15,4.3527485 15,7.2812502 L 15,8.0000002 L 13.15625,8.0000002 C 9.7507553,8.0000002 7,10.750759 7,14.15625 L 7,39.84375 C 7,43.24924 9.7507558,46 13.15625,46 L 33.84375,46 C 37.249245,46 39.999999,43.24924 40,39.84375 L 40,14.15625 C 40,10.75076 37.249243,8.0000002 33.84375,8.0000002 L 32,8.0000002 L 32,7.2812502 C 32,4.3527485 29.647252,2.0000002 26.71875,2.0000002 L 20.28125,2.0000002 z"
-        android:rotation="0"
-        android:stroke="#ff8833"
-        android:strokeWidth="1" />
-    <path
-        android:name="spark"
-        android:fill="#FFFF0000"
-        android:pathData="M 30,18.031528 L 25.579581,23.421071 L 29.370621,26.765348 L 20.096792,37 L 21.156922,28.014053 L 17,24.902844 L 20.880632,18 L 30,18.031528 z" />
+    <group>
+        <path
+            android:name="battery"
+            android:fill="#3388ff"
+            android:pathData="M 20.28125,2.0000002 C 17.352748,2.0000002 15,4.3527485 15,7.2812502 L 15,8.0000002 L 13.15625,8.0000002 C 9.7507553,8.0000002 7,10.750759 7,14.15625 L 7,39.84375 C 7,43.24924 9.7507558,46 13.15625,46 L 33.84375,46 C 37.249245,46 39.999999,43.24924 40,39.84375 L 40,14.15625 C 40,10.75076 37.249243,8.0000002 33.84375,8.0000002 L 32,8.0000002 L 32,7.2812502 C 32,4.3527485 29.647252,2.0000002 26.71875,2.0000002 L 20.28125,2.0000002 z"
+            android:stroke="#ff8833"
+            android:strokeWidth="1" />
+        <path
+            android:name="spark"
+            android:fill="#FFFF0000"
+            android:pathData="M 30,18.031528 L 25.579581,23.421071 L 29.370621,26.765348 L 20.096792,37 L 21.156922,28.014053 L 17,24.902844 L 20.880632,18 L 30,18.031528 z" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable12.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable12.xml
index 0a0407d..e28ec41 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable12.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable12.xml
@@ -23,20 +23,83 @@
         android:viewportHeight="600"
         android:viewportWidth="600" />
 
-    <path
-        android:name="pie1"
-        android:pathData="M300,70 a230,230 0 1,0 1,0 z"
-        android:stroke="#FF00FF00"
-        android:strokeWidth="70"
-        android:trimPathEnd=".75"
-        android:trimPathOffset="0"
-        android:trimPathStart="0" />
-    <path
-        android:name="v"
-        android:fill="#FF00FF00"
-        android:pathData="M300,70 l 0,-70 70,70 -70,70z"
-        android:pivotX="300"
-        android:pivotY="300"
-        android:rotation="0" />
+    <group
+        android:name="rotationGroup"
+        android:pivotX="300.0"
+        android:pivotY="300.0"
+        android:rotation="45.0" >
+        <path
+            android:name="pie1"
+            android:fill="#00000000"
+            android:pathData="M300,70 a230,230 0 1,0 1,0 z"
+            android:stroke="#FF00FF00"
+            android:strokeWidth="70"
+            android:trimPathEnd=".75"
+            android:trimPathOffset="0"
+            android:trimPathStart="0" />
+        <path
+            android:name="v"
+            android:fill="#FF00FF00"
+            android:pathData="M300,70 l 0,-70 70,70 -70,70z" />
+
+        <group
+            android:name="translateToCenterGroup"
+            android:rotation="0.0"
+            android:translateX="200.0"
+            android:translateY="200.0" >
+            <path
+                android:name="twoLines"
+                android:pathData="@string/twoLinePathData"
+                android:stroke="#FFFF0000"
+                android:strokeWidth="20" />
+
+            <group
+                android:name="rotationGroup2"
+                android:pivotX="0.0"
+                android:pivotY="0.0"
+                android:rotation="-45.0" >
+                <path
+                    android:name="twoLines1"
+                    android:pathData="@string/twoLinePathData"
+                    android:stroke="#FF00FF00"
+                    android:strokeWidth="20" />
+
+                <group
+                    android:name="translateGroupHalf"
+                    android:translateX="65.0"
+                    android:translateY="80.0" >
+                    <group
+                        android:name="rotationGroup3"
+                        android:pivotX="-65.0"
+                        android:pivotY="-80.0"
+                        android:rotation="-45.0" >
+                        <path
+                            android:name="twoLines2"
+                            android:fill="#FF00FF00"
+                            android:pathData="@string/twoLinePathData"
+                            android:stroke="#FF00FF00"
+                            android:strokeWidth="20" />
+
+                        <group
+                            android:name="translateGroup"
+                            android:translateX="65.0"
+                            android:translateY="80.0" >
+                            <group
+                                android:name="rotationGroupBlue"
+                                android:pivotX="-65.0"
+                                android:pivotY="-80.0"
+                                android:rotation="-45.0" >
+                                <path
+                                    android:name="twoLines3"
+                                    android:pathData="@string/twoLinePathData"
+                                    android:stroke="#FF0000FF"
+                                    android:strokeWidth="20" />
+                            </group>
+                        </group>
+                    </group>
+                </group>
+            </group>
+        </group>
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable13.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable13.xml
index 385b1e9..8c946df 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable13.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable13.xml
@@ -23,20 +23,19 @@
         android:viewportHeight="400"
         android:viewportWidth="600" />
 
-    <path
-        android:name="pie1"
-        android:fill="#ffffffff"
-        android:pathData="M300,200 h-150 a150,150 0 1,0 150,-150 z"
-        android:stroke="#FF00FF00"
-        android:strokeWidth="1" />
-    <path
-        android:name="half"
-        android:fill="#FFFF0000"
-        android:pathData="M275,175 v-150 a150,150 0 0,0 -150,150 z"
-        android:pivotX="300"
-        android:pivotY="200"
-        android:rotation="0"
-        android:stroke="#FF0000FF"
-        android:strokeWidth="5" />
+    <group>
+        <path
+            android:name="pie1"
+            android:fill="#ffffffff"
+            android:pathData="M300,200 h-150 a150,150 0 1,0 150,-150 z"
+            android:stroke="#FF00FF00"
+            android:strokeWidth="1" />
+        <path
+            android:name="half"
+            android:fill="#FFFF0000"
+            android:pathData="M275,175 v-150 a150,150 0 0,0 -150,150 z"
+            android:stroke="#FF0000FF"
+            android:strokeWidth="5" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable14.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable14.xml
index b701b35..8d4ca61 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable14.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable14.xml
@@ -23,17 +23,20 @@
         android:viewportHeight="500"
         android:viewportWidth="800" />
 
-    <path
-        android:name="pie2"
-        android:pathData="M200,350 l 50,-25
+    <group
+        android:pivotX="90"
+        android:pivotY="100"
+        android:rotation="20">
+        <path
+            android:name="pie2"
+            android:pathData="M200,350 l 50,-25
            a25,12 -30 0,1 100,-50 l 50,-25
            a25,25 -30 0,1 100,-50 l 50,-25
            a25,37 -30 0,1 100,-50 l 50,-25
            a25,50 -30 0,1 100,-50 l 50,-25"
-        android:pivotX="90"
-        android:pivotY="100"
-        android:rotation="20"
-        android:stroke="#FF00FF00"
-        android:strokeWidth="10" />
+            android:fill="#00000000"
+            android:stroke="#FF00FF00"
+            android:strokeWidth="10" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable15.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable15.xml
index 8d773e1..b08e157 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable15.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable15.xml
@@ -23,14 +23,16 @@
         android:viewportHeight="400"
         android:viewportWidth="500" />
 
-    <path
-        android:name="house"
-        android:fill="#ff440000"
-        android:pathData="M100,200 C100,100 250,100 250,200 S400,300 400,200"
+    <group
         android:pivotX="250"
         android:pivotY="200"
-        android:rotation="180"
-        android:stroke="#FFFF0000"
-        android:strokeWidth="10" />
+        android:rotation="180">
+        <path
+            android:name="house"
+            android:fill="#ff440000"
+            android:pathData="M100,200 C100,100 250,100 250,200 S400,300 400,200"
+            android:stroke="#FFFF0000"
+            android:strokeWidth="10" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable16.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable16.xml
index 3b7926c..ae85d9b 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable16.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable16.xml
@@ -23,13 +23,29 @@
         android:viewportHeight="200"
         android:viewportWidth="200" />
 
-    <path
-        android:name="house"
-        android:pathData="M 100,10 v 90 M 10,100 h 90"
+    <group>
+        <path
+            android:name="background1"
+            android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z"
+            android:fill="#FF000000"/>
+        <path
+            android:name="background2"
+            android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z"
+            android:fill="#FF000000"/>
+    </group>
+    <group
         android:pivotX="100"
         android:pivotY="100"
-        android:rotation="360"
-        android:stroke="#FF00FF00"
-        android:strokeWidth="10" />
+        android:rotation="90"
+        android:scaleX="0.75"
+        android:scaleY="0.5"
+        android:translateX="0.0"
+        android:translateY="100.0">
+        <path
+            android:name="twoLines"
+            android:pathData="M 100,10 v 90 M 10,100 h 90"
+            android:stroke="#FF00FF00"
+            android:strokeWidth="10" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable17.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable17.xml
index 1ec72be..c28aff4 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable17.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable17.xml
@@ -1,5 +1,4 @@
-<!--
- Copyright (C) 2014 The Android Open Source Project
+<!-- Copyright (C) 2014 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -16,20 +15,19 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android" >
 
     <size
-        android:height="64dp"
-        android:width="64dp" />
+            android:width="64dp"
+            android:height="64dp"/>
 
-    <viewport
-        android:viewportHeight="600"
-        android:viewportWidth="1200" />
+    <viewport android:viewportWidth="1200"
+          android:viewportHeight="600"/>
 
-    <path
-        android:name="house"
-        android:pathData="M200,300 Q400,50 600,300 T1000,300"
-        android:pivotX="600"
-        android:pivotY="300"
-        android:rotation="360"
-        android:stroke="#FFFF0000"
-        android:strokeWidth="10" />
+    <group>
+        <path
+                android:name="house"
+                android:pathData="M200,300 Q400,50 600,300 T1000,300"
+                android:fill="#00000000"
+                android:stroke="#FFFF0000"
+                android:strokeWidth="10"/>
+    </group>
 
-</vector>
\ No newline at end of file
+</vector>
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable18.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable18.xml
index 12d0e93..d7042fd 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable18.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable18.xml
@@ -23,13 +23,13 @@
         android:viewportHeight="400"
         android:viewportWidth="500" />
 
-    <path
-        android:name="house"
-        android:pathData="M100,200 C100,100 250,100 250,200 S400,300 400,200"
-        android:pivotX="250"
-        android:pivotY="200"
-        android:rotation="360"
-        android:stroke="#FFFFFF00"
-        android:strokeWidth="10" />
+    <group>
+        <path
+            android:name="house"
+            android:pathData="M100,200 C100,100 250,100 250,200 S400,300 400,200"
+            android:fill="#00000000"
+            android:stroke="#FFFFFF00"
+            android:strokeWidth="10" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable19.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable19.xml
index 017e04c..47a9574 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable19.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable19.xml
@@ -23,12 +23,15 @@
         android:viewportHeight="800"
         android:viewportWidth="1000" />
 
-    <path
-        android:name="house"
-        android:pathData="M10,300 Q400,550 600,300 T1000,300"
-        android:pivotX="90"
-        android:pivotY="100"
-        android:stroke="#FFFF0000"
-        android:strokeWidth="60" />
+    <group>
+        <path
+            android:name="house"
+            android:pathData="M10,300 Q400,550 600,300 T1000,300"
+            android:pivotX="90"
+            android:pivotY="100"
+            android:fill="#00000000"
+            android:stroke="#FFFF0000"
+            android:strokeWidth="60" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable20.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable20.xml
index b7002a3..b8af7e2 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable20.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable20.xml
@@ -23,14 +23,16 @@
         android:viewportHeight="480"
         android:viewportWidth="480" />
 
-    <path
-        android:name="edit"
-        android:fill="#FF00FFFF"
-        android:pathData="M406.667,180c0,0 -100 -100 -113.334 -113.333
+    <group>
+        <path
+            android:name="edit"
+            android:fill="#FF00FFFF"
+            android:pathData="M406.667,180c0,0 -100 -100 -113.334 -113.333
     c-13.333 -13.334 -33.333,0 -33.333,0l-160,160c0,0 -40,153.333 -40,173.333c0,13.333,13.333,13.333,13.333,13.333l173.334 -40
     c0,0,146.666 -146.666,160 -160C420,200,406.667,180,406.667,180z M226.399,356.823L131.95,378.62l-38.516 -38.522
     c7.848 -34.675,20.152 -82.52,23.538 -95.593l3.027,2.162l106.667,106.666L226.399,356.823z"
-        android:stroke="#FF000000"
-        android:strokeWidth="10" />
+            android:stroke="#FF000000"
+            android:strokeWidth="10" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable21.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable21.xml
new file mode 100644
index 0000000..e0013e7
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable21.xml
@@ -0,0 +1,51 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <size
+        android:height="64dp"
+        android:width="64dp" />
+
+    <viewport
+        android:viewportHeight="200"
+        android:viewportWidth="200" />
+
+    <group>
+        <path
+            android:name="background1"
+            android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z"
+            android:fill="#FF000000"/>
+        <path
+            android:name="background2"
+            android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z"
+            android:fill="#FF000000"/>
+    </group>
+    <group
+        android:pivotX="0"
+        android:pivotY="0"
+        android:rotation="90"
+        android:scaleX="0.75"
+        android:scaleY="0.5"
+        android:translateX="100.0"
+        android:translateY="100.0">
+        <path
+            android:name="twoLines"
+            android:pathData="M 100,10 v 90 M 10,100 h 90"
+            android:stroke="#FF00FF00"
+            android:strokeWidth="10" />
+    </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable22.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable22.xml
new file mode 100644
index 0000000..8d38cb5
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable22.xml
@@ -0,0 +1,72 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <size
+        android:height="64dp"
+        android:width="64dp" />
+
+    <viewport
+        android:viewportHeight="400"
+        android:viewportWidth="400" />
+
+    <group android:name="backgroundGroup" >
+        <path
+            android:name="background1"
+            android:fill="#80000000"
+            android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+        <path
+            android:name="background2"
+            android:fill="#80000000"
+            android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
+    </group>
+    <group
+        android:name="translateToCenterGroup"
+        android:translateX="50.0"
+        android:translateY="90.0" >
+        <path
+            android:name="twoLines"
+            android:pathData="M 0,0 v 100 M 0,0 h 100"
+            android:stroke="#FFFF0000"
+            android:strokeWidth="20" />
+
+        <group
+            android:name="rotationGroup"
+            android:pivotX="0.0"
+            android:pivotY="0.0"
+            android:rotation="-45.0" >
+            <path
+                android:name="twoLines1"
+                android:pathData="M 0,0 v 100 M 0,0 h 100"
+                android:stroke="#FF00FF00"
+                android:strokeWidth="20" />
+
+            <group
+                android:name="translateGroup"
+                android:translateX="130.0"
+                android:translateY="160.0" >
+                <group android:name="scaleGroup" >
+                    <path
+                        android:name="twoLines2"
+                        android:pathData="M 0,0 v 100 M 0,0 h 100"
+                        android:stroke="#FF0000FF"
+                        android:strokeWidth="20" />
+                </group>
+            </group>
+        </group>
+    </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable23.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable23.xml
new file mode 100644
index 0000000..52acd7a
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable23.xml
@@ -0,0 +1,86 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <size
+        android:height="64dp"
+        android:width="64dp" />
+
+    <viewport
+        android:viewportHeight="400"
+        android:viewportWidth="400" />
+
+    <group android:name="backgroundGroup" >
+        <path
+            android:name="background1"
+            android:fill="#80000000"
+            android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+        <path
+            android:name="background2"
+            android:fill="#80000000"
+            android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
+    </group>
+    <group
+        android:name="translateToCenterGroup"
+        android:translateX="50.0"
+        android:translateY="90.0" >
+        <path
+            android:name="twoLines"
+            android:pathData="@string/twoLinePathData"
+            android:stroke="#FFFF0000"
+            android:strokeWidth="20" />
+
+        <group
+            android:name="rotationGroup"
+            android:pivotX="0.0"
+            android:pivotY="0.0"
+            android:rotation="-45.0" >
+            <path
+                android:name="twoLines1"
+                android:pathData="@string/twoLinePathData"
+                android:stroke="#FF00FF00"
+                android:strokeWidth="20" />
+
+            <group
+                android:name="translateGroup"
+                android:translateX="130.0"
+                android:translateY="160.0" >
+                <group android:name="scaleGroup" >
+                    <path
+                        android:name="twoLines3"
+                        android:pathData="@string/twoLinePathData"
+                        android:stroke="#FF0000FF"
+                        android:strokeWidth="20" />
+                </group>
+            </group>
+
+            <group
+                android:name="translateGroupHalf"
+                android:translateX="65.0"
+                android:translateY="80.0" >
+                <group android:name="scaleGroup" >
+                    <path
+                        android:name="twoLines2"
+                        android:pathData="@string/twoLinePathData"
+                        android:fill="?android:attr/colorForeground"
+                        android:stroke="?android:attr/colorForeground"
+                        android:strokeWidth="20" />
+                </group>
+            </group>
+        </group>
+    </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable24.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable24.xml
new file mode 100644
index 0000000..c062d70
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable24.xml
@@ -0,0 +1,91 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <size
+        android:height="64dp"
+        android:width="64dp" />
+
+    <viewport
+        android:viewportHeight="400"
+        android:viewportWidth="400" />
+
+    <group android:name="backgroundGroup"
+        android:alpha = "0.5" >
+        <path
+            android:name="background1"
+            android:fill="#FF000000"
+            android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+        <path
+            android:name="background2"
+            android:fill="#FF000000"
+            android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
+    </group>
+    <group
+        android:name="translateToCenterGroup"
+        android:translateX="50.0"
+        android:translateY="90.0"
+        android:alpha = "0.5" >
+        <path
+            android:name="twoLines"
+            android:pathData="@string/twoLinePathData"
+            android:stroke="#FFFF0000"
+            android:strokeWidth="20" />
+
+        <group
+            android:name="rotationGroup"
+            android:pivotX="0.0"
+            android:pivotY="0.0"
+            android:rotation="-45.0"
+            android:alpha = "0.5" >
+            <path
+                android:name="twoLines1"
+                android:pathData="@string/twoLinePathData"
+                android:stroke="#FF00FF00"
+                android:strokeWidth="20" />
+
+            <group
+                android:name="translateGroup"
+                android:translateX="130.0"
+                android:translateY="160.0"
+                android:alpha = "0.5">
+                <group android:name="scaleGroup" >
+                    <path
+                        android:name="twoLines3"
+                        android:pathData="@string/twoLinePathData"
+                        android:stroke="#FF0000FF"
+                        android:strokeWidth="20" />
+                </group>
+            </group>
+
+            <group
+                android:name="translateGroupHalf"
+                android:translateX="65.0"
+                android:translateY="80.0"
+                android:alpha = "0.5">
+                <group android:name="scaleGroup" >
+                    <path
+                        android:name="twoLines2"
+                        android:pathData="@string/twoLinePathData"
+                        android:fill="?android:attr/colorForeground"
+                        android:stroke="?android:attr/colorForeground"
+                        android:strokeWidth="20" />
+                </group>
+            </group>
+        </group>
+    </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_create.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_create.xml
index cda213d..22ce795 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_icon_create.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_icon_create.xml
@@ -23,8 +23,10 @@
         android:viewportHeight="24"
         android:viewportWidth="24" />
 
-    <path
-        android:fill="#FF000000"
-        android:pathData="M3.0,17.25L3.0,21.0l3.75,0.0L17.813995,9.936001l-3.75,-3.75L3.0,17.25zM20.707,7.0429993c0.391,-0.391 0.391,-1.023 0.0,-1.414l-2.336,-2.336c-0.391,-0.391 -1.023,-0.391 -1.414,0.0l-1.832,1.832l3.75,3.75L20.707,7.0429993z" />
+    <group>
+        <path
+            android:fill="#FF000000"
+            android:pathData="M3.0,17.25L3.0,21.0l3.75,0.0L17.813995,9.936001l-3.75,-3.75L3.0,17.25zM20.707,7.0429993c0.391,-0.391 0.391,-1.023 0.0,-1.414l-2.336,-2.336c-0.391,-0.391 -1.023,-0.391 -1.414,0.0l-1.832,1.832l3.75,3.75L20.707,7.0429993z" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_delete.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_delete.xml
index 2cb6381..042173c 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_icon_delete.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_icon_delete.xml
@@ -23,8 +23,10 @@
         android:viewportHeight="24"
         android:viewportWidth="24" />
 
-    <path
-        android:fill="#FF000000"
-        android:pathData="M6.0,19.0c0.0,1.104 0.896,2.0 2.0,2.0l8.0,0.0c1.104,0.0 2.0,-0.896 2.0,-2.0l0.0,-12.0L6.0,7.0L6.0,19.0zM18.0,4.0l-2.5,0.0l-1.0,-1.0l-5.0,0.0l-1.0,1.0L6.0,4.0C5.4469986,4.0 5.0,4.4469986 5.0,5.0l0.0,1.0l14.0,0.0l0.0,-1.0C19.0,4.4469986 18.552002,4.0 18.0,4.0z" />
+    <group>
+        <path
+            android:fill="#FF000000"
+            android:pathData="M6.0,19.0c0.0,1.104 0.896,2.0 2.0,2.0l8.0,0.0c1.104,0.0 2.0,-0.896 2.0,-2.0l0.0,-12.0L6.0,7.0L6.0,19.0zM18.0,4.0l-2.5,0.0l-1.0,-1.0l-5.0,0.0l-1.0,1.0L6.0,4.0C5.4469986,4.0 5.0,4.4469986 5.0,5.0l0.0,1.0l14.0,0.0l0.0,-1.0C19.0,4.4469986 18.552002,4.0 18.0,4.0z" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_heart.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_heart.xml
index d58942e..6b6f43d 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_icon_heart.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_icon_heart.xml
@@ -23,8 +23,10 @@
         android:viewportHeight="24"
         android:viewportWidth="24" />
 
-    <path
-        android:fill="#FF000000"
-        android:pathData="M16.0,5.0c-1.955,0.0 -3.83,1.268 -4.5,3.0c-0.67,-1.732 -2.547,-3.0 -4.5,-3.0C4.4570007,5.0 2.5,6.931999 2.5,9.5c0.0,3.529 3.793,6.258 9.0,11.5c5.207,-5.242 9.0,-7.971 9.0,-11.5C20.5,6.931999 18.543,5.0 16.0,5.0z" />
+    <group>
+        <path
+            android:fill="#FF000000"
+            android:pathData="M16.0,5.0c-1.955,0.0 -3.83,1.268 -4.5,3.0c-0.67,-1.732 -2.547,-3.0 -4.5,-3.0C4.4570007,5.0 2.5,6.931999 2.5,9.5c0.0,3.529 3.793,6.258 9.0,11.5c5.207,-5.242 9.0,-7.971 9.0,-11.5C20.5,6.931999 18.543,5.0 16.0,5.0z" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_schedule.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_schedule.xml
index 4717be4..ba8ebca 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_icon_schedule.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_icon_schedule.xml
@@ -23,11 +23,13 @@
         android:viewportHeight="24"
         android:viewportWidth="24" />
 
-    <path
-        android:fillOpacity="0.9"
-        android:pathData="M11.994999,2.0C6.4679985,2.0 2.0,6.4780006 2.0,12.0s4.468,10.0 9.995,10.0S22.0,17.522 22.0,12.0S17.521,2.0 11.994999,2.0zM12.0,20.0c-4.42,0.0 -8.0,-3.582 -8.0,-8.0s3.58,-8.0 8.0,-8.0s8.0,3.582 8.0,8.0S16.419998,20.0 12.0,20.0z" />
-    <path
-        android:fillOpacity="0.9"
-        android:pathData="M12.5,6.0l-1.5,0.0 0.0,7.0 5.3029995,3.1819992 0.75,-1.249999 -4.5529995,-2.7320004z" />
+    <group>
+        <path
+            android:fillOpacity="0.9"
+            android:pathData="M11.994999,2.0C6.4679985,2.0 2.0,6.4780006 2.0,12.0s4.468,10.0 9.995,10.0S22.0,17.522 22.0,12.0S17.521,2.0 11.994999,2.0zM12.0,20.0c-4.42,0.0 -8.0,-3.582 -8.0,-8.0s3.58,-8.0 8.0,-8.0s8.0,3.582 8.0,8.0S16.419998,20.0 12.0,20.0z" />
+        <path
+            android:fillOpacity="0.9"
+            android:pathData="M12.5,6.0l-1.5,0.0 0.0,7.0 5.3029995,3.1819992 0.75,-1.249999 -4.5529995,-2.7320004z" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_settings.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_settings.xml
index c626325..896a938 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_icon_settings.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_icon_settings.xml
@@ -23,8 +23,10 @@
         android:viewportHeight="24"
         android:viewportWidth="24" />
 
-    <path
-        android:fill="#FF000000"
-        android:pathData="M19.429,12.975998c0.042,-0.32 0.07,-0.645 0.07,-0.976s-0.029,-0.655 -0.07,-0.976l2.113,-1.654c0.188,-0.151 0.243,-0.422 0.118,-0.639l-2.0,-3.463c-0.125,-0.217 -0.386,-0.304 -0.612,-0.218l-2.49,1.004c-0.516,-0.396 -1.081,-0.731 -1.69,-0.984l-0.375,-2.648C14.456,2.1829987 14.25,2.0 14.0,2.0l-4.0,0.0C9.75,2.0 9.544,2.1829987 9.506,2.422001L9.131,5.0699997C8.521,5.322998 7.957,5.6570015 7.44,6.054001L4.952,5.0509987C4.726,4.965 4.464,5.052002 4.34,5.269001l-2.0,3.463C2.2150002,8.947998 2.27,9.219002 2.4580002,9.369999l2.112,1.653C4.528,11.344002 4.5,11.668999 4.5,12.0s0.029,0.656 0.071,0.977L2.4580002,14.630001c-0.188,0.151 -0.243,0.422 -0.118,0.639l2.0,3.463c0.125,0.217 0.386,0.304 0.612,0.218l2.489,-1.004c0.516,0.396 1.081,0.731 1.69,0.984l0.375,2.648C9.544,21.817001 9.75,22.0 10.0,22.0l4.0,0.0c0.25,0.0 0.456,-0.183 0.494,-0.422l0.375,-2.648c0.609,-0.253 1.174,-0.588 1.689,-0.984l2.49,1.004c0.226,0.086 0.487,-0.001 0.612,-0.218l2.0,-3.463c0.125,-0.217 0.07,-0.487 -0.118,-0.639L19.429,12.975998zM12.0,16.0c-2.21,0.0 -4.0,-1.791 -4.0,-4.0c0.0,-2.21 1.79,-4.0 4.0,-4.0c2.208,0.0 4.0,1.79 4.0,4.0C16.0,14.209 14.208,16.0 12.0,16.0z" />
+    <group>
+        <path
+            android:fill="#FF000000"
+            android:pathData="M19.429,12.975998c0.042,-0.32 0.07,-0.645 0.07,-0.976s-0.029,-0.655 -0.07,-0.976l2.113,-1.654c0.188,-0.151 0.243,-0.422 0.118,-0.639l-2.0,-3.463c-0.125,-0.217 -0.386,-0.304 -0.612,-0.218l-2.49,1.004c-0.516,-0.396 -1.081,-0.731 -1.69,-0.984l-0.375,-2.648C14.456,2.1829987 14.25,2.0 14.0,2.0l-4.0,0.0C9.75,2.0 9.544,2.1829987 9.506,2.422001L9.131,5.0699997C8.521,5.322998 7.957,5.6570015 7.44,6.054001L4.952,5.0509987C4.726,4.965 4.464,5.052002 4.34,5.269001l-2.0,3.463C2.2150002,8.947998 2.27,9.219002 2.4580002,9.369999l2.112,1.653C4.528,11.344002 4.5,11.668999 4.5,12.0s0.029,0.656 0.071,0.977L2.4580002,14.630001c-0.188,0.151 -0.243,0.422 -0.118,0.639l2.0,3.463c0.125,0.217 0.386,0.304 0.612,0.218l2.489,-1.004c0.516,0.396 1.081,0.731 1.69,0.984l0.375,2.648C9.544,21.817001 9.75,22.0 10.0,22.0l4.0,0.0c0.25,0.0 0.456,-0.183 0.494,-0.422l0.375,-2.648c0.609,-0.253 1.174,-0.588 1.689,-0.984l2.49,1.004c0.226,0.086 0.487,-0.001 0.612,-0.218l2.0,-3.463c0.125,-0.217 0.07,-0.487 -0.118,-0.639L19.429,12.975998zM12.0,16.0c-2.21,0.0 -4.0,-1.791 -4.0,-4.0c0.0,-2.21 1.79,-4.0 4.0,-4.0c2.208,0.0 4.0,1.79 4.0,4.0C16.0,14.209 14.208,16.0 12.0,16.0z" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_test01.xml b/tests/VectorDrawableTest/res/drawable/vector_test01.xml
index bad5a46..fc2a15c 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_test01.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_test01.xml
@@ -23,10 +23,13 @@
         android:viewportHeight="512"
         android:viewportWidth="512" />
 
-    <path
-        android:name="002b"
-        android:pathData="M100,200c0,-100 150,-100 150,0s150,100 150,0t-200,299"
-        android:stroke="#FF0000FF"
-        android:strokeWidth="4" />
+    <group>
+        <path
+            android:name="002b"
+            android:pathData="M100,200c0,-100 150,-100 150,0s150,100 150,0t-200,299"
+            android:stroke="#FF0000FF"
+            android:strokeWidth="4"
+            android:fill="#00000000" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_test02.xml b/tests/VectorDrawableTest/res/drawable/vector_test02.xml
index c92b6f4..9f4abbf 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_test02.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_test02.xml
@@ -23,10 +23,13 @@
         android:viewportHeight="512"
         android:viewportWidth="512" />
 
-    <path
-        android:name="002b"
-        android:pathData="M100,200c0,-100 150,-100 150,0s150,100 150,0T-200,299"
-        android:stroke="#FF0000FF"
-        android:strokeWidth="4" />
+    <group>
+        <path
+            android:name="002b"
+            android:pathData="M100,200c0,-100 150,-100 150,0s150,100 150,0T-200,299"
+            android:stroke="#FF0000FF"
+            android:strokeWidth="4"
+            android:fill="#00000000" />
+    </group>
 
 </vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/values/strings.xml b/tests/VectorDrawableTest/res/values/strings.xml
index 64163c2..b49a1aa 100644
--- a/tests/VectorDrawableTest/res/values/strings.xml
+++ b/tests/VectorDrawableTest/res/values/strings.xml
@@ -15,4 +15,5 @@
 -->
 
 <resources>
+    <string name="twoLinePathData" >"M 0,0 v 100 M 0,0 h 100"</string>
 </resources>
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java
new file mode 100644
index 0000000..6e864fa
--- /dev/null
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.test.dynamic;
+
+import android.app.Activity;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+public class AnimatedVectorDrawableTest extends Activity {
+    private static final String LOGCAT = "VectorDrawableAnimationTest";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Button button = new Button(this);
+        button.setBackgroundResource(R.drawable.animation_vector_drawable01);
+        button.setOnClickListener(new View.OnClickListener() {
+                @Override
+            public void onClick(View v) {
+                AnimatedVectorDrawable frameAnimation = (AnimatedVectorDrawable) v.getBackground();
+                frameAnimation.start();
+            }
+        });
+
+        setContentView(button);
+    }
+}
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawable01.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawable01.java
index 7ba01b1..a23d819 100644
--- a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawable01.java
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawable01.java
@@ -61,6 +61,8 @@
             button.setWidth(200);
             button.setBackgroundResource(icon[i]);
             container.addView(button);
+            VectorDrawable vd = (VectorDrawable) button.getBackground();
+            vd.setAlpha((i + 1) * (0xFF / (icon.length + 1)));
         }
 
         setContentView(container);
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawableAnimation.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawableAnimation.java
new file mode 100644
index 0000000..93b06b6
--- /dev/null
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawableAnimation.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.test.dynamic;
+
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.graphics.drawable.AnimationDrawable;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+public class VectorDrawableAnimation extends Activity {
+    private static final String LOGCAT = "VectorDrawableAnimation";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final Button button = new Button(this);
+        button.setBackgroundResource(R.drawable.animation_drawable_vector);
+
+        button.setOnClickListener(new View.OnClickListener() {
+                @Override
+            public void onClick(View v) {
+                AnimationDrawable frameAnimation = (AnimationDrawable) v.getBackground();
+                // Start the animation (looped playback by default).
+                frameAnimation.start();
+            }
+        });
+
+        setContentView(button);
+    }
+
+}
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java
index b918cdd..814deb8 100644
--- a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java
@@ -17,11 +17,11 @@
 import android.content.res.Resources;
 import android.graphics.drawable.VectorDrawable;
 import android.os.Bundle;
-import android.view.View;
 import android.widget.TextView;
 import android.widget.Button;
 import android.widget.GridLayout;
 import android.widget.ScrollView;
+
 import java.text.DecimalFormat;
 
 @SuppressWarnings({"UnusedDeclaration"})
@@ -47,7 +47,11 @@
             R.drawable.vector_drawable17,
             R.drawable.vector_drawable18,
             R.drawable.vector_drawable19,
-            R.drawable.vector_drawable20
+            R.drawable.vector_drawable20,
+            R.drawable.vector_drawable21,
+            R.drawable.vector_drawable22,
+            R.drawable.vector_drawable23,
+            R.drawable.vector_drawable24,
     };
 
     @Override
@@ -68,7 +72,6 @@
         TextView t = new TextView(this);
         DecimalFormat df = new DecimalFormat("#.##");
         t.setText("avgL=" + df.format(time / (icon.length * 1000000.)) + " ms");
-        t.setBackgroundColor(0xFF000000);
         container.addView(t);
         time =  android.os.SystemClock.elapsedRealtimeNanos();
         for (int i = 0; i < icon.length; i++) {
@@ -81,7 +84,6 @@
         time =  android.os.SystemClock.elapsedRealtimeNanos()-time;
         t = new TextView(this);
         t.setText("avgS=" + df.format(time / (icon.length * 1000000.)) + " ms");
-        t.setBackgroundColor(0xFF000000);
         container.addView(t);
     }
 }
diff --git a/tests/VoiceEnrollment/Android.mk b/tests/VoiceEnrollment/Android.mk
new file mode 100644
index 0000000..2ab3d02
--- /dev/null
+++ b/tests/VoiceEnrollment/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := VoiceEnrollment
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PRIVILEGED_MODULE := true
+
+include $(BUILD_PACKAGE)
diff --git a/tests/VoiceEnrollment/AndroidManifest.xml b/tests/VoiceEnrollment/AndroidManifest.xml
new file mode 100644
index 0000000..6321222
--- /dev/null
+++ b/tests/VoiceEnrollment/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.test.voiceenrollment">
+
+    <application
+        android:permission="android.permission.MANAGE_VOICE_KEYPHRASES">
+        <activity android:name="TestEnrollmentActivity" android:label="Voice Enrollment Application"
+                  android:theme="@android:style/Theme.Material.Light.Voice">
+            <intent-filter>
+                <action android:name="com.android.intent.action.MANAGE_VOICE_KEYPHRASES" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <meta-data android:name="android.voice_enrollment"
+            android:resource="@xml/enrollment_application"/>
+    </application>
+</manifest>
diff --git a/tests/VoiceEnrollment/res/xml/enrollment_application.xml b/tests/VoiceEnrollment/res/xml/enrollment_application.xml
new file mode 100644
index 0000000..710a0ac
--- /dev/null
+++ b/tests/VoiceEnrollment/res/xml/enrollment_application.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+
+<voice-enrollment-application xmlns:android="http://schemas.android.com/apk/res/android"
+    android:searchKeyphraseId="101"
+    android:searchKeyphrase="Hello There"
+    android:searchKeyphraseSupportedLocales="en-US,en-GB,fr-FR,de-DE" />
diff --git a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java
new file mode 100644
index 0000000..7fbd965
--- /dev/null
+++ b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.voiceenrollment;
+
+import android.app.Activity;
+
+public class TestEnrollmentActivity extends Activity {
+    // TODO(sansid): Add a test enrollment flow here.
+}
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
index d40b05f..00c2c64 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
@@ -21,6 +21,8 @@
 import android.service.voice.VoiceInteractionService;
 import android.util.Log;
 
+import java.util.Arrays;
+
 public class MainInteractionService extends VoiceInteractionService {
     static final String TAG = "MainInteractionService";
 
@@ -28,6 +30,9 @@
     public void onCreate() {
         super.onCreate();
         Log.i(TAG, "Creating " + this);
+        Log.i(TAG, "Keyphrase enrollment error? " + getKeyphraseEnrollmentInfo().getParseError());
+        Log.i(TAG, "Keyphrase enrollment meta-data: "
+                + Arrays.toString(getKeyphraseEnrollmentInfo().getKeyphrases()));
     }
 
     @Override
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 5a60014..322d86c 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -105,20 +105,12 @@
         "            en\n"
         "            port,en\n"
         "            port,land,en_US\n"
-        "       If you put the special locale, zz_ZZ on the list, it will perform\n"
-        "       pseudolocalization on the default locale, modifying all of the\n"
-        "       strings so you can look for strings that missed the\n"
-        "       internationalization process.  For example:\n"
-        "            port,land,zz_ZZ\n"
         "   -d  one or more device assets to include, separated by commas\n"
         "   -f  force overwrite of existing files\n"
         "   -g  specify a pixel tolerance to force images to grayscale, default 0\n"
         "   -j  specify a jar or zip file containing classes to include\n"
         "   -k  junk path of file(s) added\n"
         "   -m  make package directories under location specified by -J\n"
-#if 0
-        "   -p  pseudolocalize the default configuration\n"
-#endif
         "   -u  update existing packages (add new, replace older, remove deleted files)\n"
         "   -v  verbose output\n"
         "   -x  create extending (non-application) resource IDs\n"
@@ -141,6 +133,8 @@
         "       manifest, making the application debuggable even on production devices.\n"
         "   --include-meta-data\n"
         "       when used with \"dump badging\" also includes meta-data tags.\n"
+        "   --pseudo-localize\n"
+        "       generate resources for pseudo-locales (en-XA and ar-XB).\n"
         "   --min-sdk-version\n"
         "       inserts android:minSdkVersion in to manifest.  If the version is 7 or\n"
         "       higher, the default encoding for resources will be in UTF-8.\n"
@@ -647,6 +641,8 @@
                         goto bail;
                     }
                     gUserIgnoreAssets = argv[0];
+                } else if (strcmp(cp, "-pseudo-localize") == 0) {
+                    bundle.setPseudolocalize(PSEUDO_ACCENTED | PSEUDO_BIDI);
                 } else {
                     fprintf(stderr, "ERROR: Unknown option '-%s'\n", cp);
                     wantUsage = true;
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index a417479..cd199dc 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -71,7 +71,7 @@
      * Returns the native delegate associated to a given {@link Canvas} object.
      */
     public static Canvas_Delegate getDelegate(Canvas canvas) {
-        return sManager.getDelegate(canvas.getNativeCanvas());
+        return sManager.getDelegate(canvas.getNativeCanvasWrapper());
     }
 
     /**
@@ -102,7 +102,7 @@
     @LayoutlibDelegate
     /*package*/ static boolean isOpaque(Canvas thisCanvas) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas());
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper());
         if (canvasDelegate == null) {
             return false;
         }
@@ -113,7 +113,7 @@
     @LayoutlibDelegate
     /*package*/ static int getWidth(Canvas thisCanvas) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas());
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper());
         if (canvasDelegate == null) {
             return 0;
         }
@@ -124,7 +124,7 @@
     @LayoutlibDelegate
     /*package*/ static int getHeight(Canvas thisCanvas) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas());
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper());
         if (canvasDelegate == null) {
             return 0;
         }
@@ -135,7 +135,7 @@
     @LayoutlibDelegate
    /*package*/ static void translate(Canvas thisCanvas, float dx, float dy) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas());
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper());
         if (canvasDelegate == null) {
             return;
         }
@@ -146,7 +146,7 @@
     @LayoutlibDelegate
     /*package*/ static void rotate(Canvas thisCanvas, float degrees) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas());
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper());
         if (canvasDelegate == null) {
             return;
         }
@@ -157,7 +157,7 @@
     @LayoutlibDelegate
    /*package*/ static void scale(Canvas thisCanvas, float sx, float sy) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas());
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper());
         if (canvasDelegate == null) {
             return;
         }
@@ -168,7 +168,7 @@
     @LayoutlibDelegate
    /*package*/ static void skew(Canvas thisCanvas, float kx, float ky) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas());
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper());
         if (canvasDelegate == null) {
             return;
         }
@@ -204,7 +204,7 @@
     /*package*/ static boolean clipRect(Canvas thisCanvas, float left, float top, float right,
             float bottom) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas());
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper());
         if (canvasDelegate == null) {
             return false;
         }
@@ -227,7 +227,7 @@
     @LayoutlibDelegate
     /*package*/ static int save(Canvas thisCanvas, int saveFlags) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas());
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper());
         if (canvasDelegate == null) {
             return 0;
         }
@@ -238,7 +238,7 @@
     @LayoutlibDelegate
     /*package*/ static void restore(Canvas thisCanvas) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas());
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper());
         if (canvasDelegate == null) {
             return;
         }
@@ -249,7 +249,7 @@
     @LayoutlibDelegate
     /*package*/ static int getSaveCount(Canvas thisCanvas) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas());
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper());
         if (canvasDelegate == null) {
             return 0;
         }
@@ -260,7 +260,7 @@
     @LayoutlibDelegate
     /*package*/ static void restoreToCount(Canvas thisCanvas, int saveCount) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvas());
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.getNativeCanvasWrapper());
         if (canvasDelegate == null) {
             return;
         }
@@ -287,7 +287,7 @@
     /*package*/ static void drawLines(Canvas thisCanvas,
             final float[] pts, final int offset, final int count,
             Paint paint) {
-        draw(thisCanvas.getNativeCanvas(), paint.mNativePaint, false /*compositeOnly*/,
+        draw(thisCanvas.getNativeCanvasWrapper(), paint.mNativePaint, false /*compositeOnly*/,
                 false /*forceSrcMode*/, new GcSnapshot.Drawable() {
                     @Override
                     public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
index 0bb7fc2..9ec6f4d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
@@ -135,7 +135,6 @@
     @Override
     public void setImeWindowStatus(IBinder arg0, int arg1, int arg2) throws RemoteException {
         // TODO Auto-generated method stub
-
     }
 
     @Override
@@ -197,25 +196,25 @@
     }
 
     @Override
-        public boolean switchToNextInputMethod(IBinder arg0, boolean arg1) throws RemoteException {
+    public boolean switchToNextInputMethod(IBinder arg0, boolean arg1) throws RemoteException {
         // TODO Auto-generated method stub
         return false;
     }
 
     @Override
-        public boolean shouldOfferSwitchingToNextInputMethod(IBinder arg0) throws RemoteException {
+    public boolean shouldOfferSwitchingToNextInputMethod(IBinder arg0) throws RemoteException {
         // TODO Auto-generated method stub
         return false;
     }
 
     @Override
-        public int getInputMethodWindowVisibleHeight() throws RemoteException {
+     public int getInputMethodWindowVisibleHeight() throws RemoteException {
         // TODO Auto-generated method stub
         return 0;
     }
 
     @Override
-        public void notifyTextCommitted() throws RemoteException {
+    public void notifyUserAction(int sequenceNumber) throws RemoteException {
         // TODO Auto-generated method stub
     }
 
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 75db8e1..c715003 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -86,6 +86,7 @@
 import android.view.ViewGroup.MarginLayoutParams;
 import android.view.ViewParent;
 import android.view.WindowManagerGlobal_Delegate;
+import android.view.ViewParent;
 import android.widget.AbsListView;
 import android.widget.AbsSpinner;
 import android.widget.ActionMenuView;
diff --git a/wifi/java/android/net/wifi/RssiPacketCountInfo.java b/wifi/java/android/net/wifi/RssiPacketCountInfo.java
index f549e1d..0de2033 100644
--- a/wifi/java/android/net/wifi/RssiPacketCountInfo.java
+++ b/wifi/java/android/net/wifi/RssiPacketCountInfo.java
@@ -34,14 +34,17 @@
 
     public int txbad;
 
+    public int rxgood;
+
     public RssiPacketCountInfo() {
-        rssi = txgood = txbad = 0;
+        rssi = txgood = txbad = rxgood = 0;
     }
 
     private RssiPacketCountInfo(Parcel in) {
         rssi = in.readInt();
         txgood = in.readInt();
         txbad = in.readInt();
+        rxgood = in.readInt();
     }
 
     @Override
@@ -49,6 +52,7 @@
         out.writeInt(rssi);
         out.writeInt(txgood);
         out.writeInt(txbad);
+        out.writeInt(rxgood);
     }
 
     @Override
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index f6d7f55..8191edd 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -89,6 +89,21 @@
      * {@hide}
      */
     public final static int UNSPECIFIED = -1;
+    /**
+     * @hide
+     * TODO: makes real freq boundaries
+     */
+    public boolean is24GHz() {
+        return frequency > 2400 && frequency < 2500;
+    }
+
+    /**
+     * @hide
+     * TODO: makes real freq boundaries
+     */
+    public boolean is5GHz() {
+        return frequency > 4900 && frequency < 5900;
+    }
 
     /** information element from beacon
      * @hide
@@ -144,8 +159,7 @@
             distanceCm = source.distanceCm;
             distanceSdCm = source.distanceSdCm;
             seen = source.seen;
-            if (source.passpoint != null)
-                passpoint = new WifiPasspointInfo(source.passpoint);
+            passpoint = source.passpoint;
         }
     }
 
@@ -179,8 +193,7 @@
         sb.append(", distanceSd: ").append((distanceSdCm != UNSPECIFIED ? distanceSdCm : "?")).
                 append("(cm)");
 
-        if (passpoint != null)
-            sb.append(", passpoint: [").append(passpoint.toString()).append("]");
+        sb.append(", passpoint: ").append(passpoint != null ? "yes" : "no");
 
         return sb.toString();
     }
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 5dfc318..48396d5 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -347,9 +347,68 @@
      */
     public HashMap<String, ScanResult> scanResultCache;
 
+    /** The Below RSSI thresholds are used to configure AutoJoin
+     *  - GOOD/LOW/BAD thresholds are used so as to calculate link score
+     *  - UNWANTED_SOFT are used by the blacklisting logic so as to handle the unwanted network message coming from CS
+     *  - UNBLACKLIST thresholds are used so as to tweak the speed at which the network is unblacklisted (i.e. if
+     *          it is seen with good RSSI, it is blacklisted faster)
+     *  - INITIAL_AUTOJOIN_ATTEMPT, used to determine how close from the network we need to be before autojoin kicks in
+     */
     /** @hide **/
     public static int INVALID_RSSI = -127;
 
+    /** @hide **/
+    public static int UNWANTED_BLACKLIST_SOFT_RSSI_24 = -80;
+
+    /** @hide **/
+    public static int UNWANTED_BLACKLIST_SOFT_RSSI_5 = -70;
+
+    /** @hide **/
+    public static int GOOD_RSSI_24 = -65;
+
+    /** @hide **/
+    public static int LOW_RSSI_24 = -75;
+
+    /** @hide **/
+    public static int BAD_RSSI_24 = -85;
+
+    /** @hide **/
+    public static int GOOD_RSSI_5 = -55;
+
+    /** @hide **/
+    public static int LOW_RSSI_5 = -65;
+
+    /** @hide **/
+    public static int BAD_RSSI_5 = -75;
+
+    /** @hide **/
+    public static int UNWANTED_BLACKLIST_SOFT_BUMP = 4;
+
+    /** @hide **/
+    public static int UNWANTED_BLACKLIST_HARD_BUMP = 8;
+
+    /** @hide **/
+    public static int UNBLACKLIST_THRESHOLD_24_SOFT = -75;
+
+    /** @hide **/
+    public static int UNBLACKLIST_THRESHOLD_24_HARD = -68;
+
+    /** @hide **/
+    public static int UNBLACKLIST_THRESHOLD_5_SOFT = -63;
+
+    /** @hide **/
+    public static int UNBLACKLIST_THRESHOLD_5_HARD = -56;
+
+    /** @hide **/
+    public static int INITIAL_AUTO_JOIN_ATTEMPT_MIN_24 = -80;
+
+    /** @hide **/
+    public static int INITIAL_AUTO_JOIN_ATTEMPT_MIN_5 = -70;
+
+    /** @hide
+     * 5GHz band is prefered over 2.4 if the 5GHz RSSI is higher than this threshold **/
+    public static int A_BAND_PREFERENCE_RSSI_THRESHOLD = -65;
+
     /**
      * @hide
      * A summary of the RSSI and Band status for that configuration
@@ -426,11 +485,11 @@
             if (result.seen == 0)
                 continue;
 
-            if ((result.frequency > 4900) && (result.frequency < 5900)) {
+            if (result.is5GHz()) {
                 //strictly speaking: [4915, 5825]
                 //number of known BSSID on 5GHz band
                 status.num5 = status.num5 + 1;
-            } else if ((result.frequency > 2400) && (result.frequency < 2500)) {
+            } else if (result.is24GHz()) {
                 //strictly speaking: [2412, 2482]
                 //number of known BSSID on 2.4Ghz band
                 status.num24 = status.num24 + 1;
@@ -438,12 +497,12 @@
 
             if ((now_ms - result.seen) > age) continue;
 
-            if ((result.frequency > 4900) && (result.frequency < 5900)) {
+            if (result.is5GHz()) {
                 if (result.level > status.rssi5) {
                     status.rssi5 = result.level;
                     status.age5 = result.seen;
                 }
-            } else if ((result.frequency > 2400) && (result.frequency < 2500)) {
+            } else if (result.is24GHz()) {
                 if (result.level > status.rssi24) {
                     status.rssi24 = result.level;
                     status.age24 = result.seen;
@@ -456,7 +515,7 @@
 
     /** @hide */
     public static final int AUTO_JOIN_ENABLED                   = 0;
-    /** @hide
+    /**
      * if this is set, the WifiConfiguration cannot use linkages so as to bump
      * it's relative priority.
      * - status between and 128 indicate various level of blacklisting depending
@@ -465,7 +524,17 @@
      * although it may have been self added we will not re-self-add it, ignore it,
      * not return it to applications, and not connect to it
      * */
+
+    /** @hide
+     * network was temporary disabled due to bad connection, most likely due
+     * to weak RSSI */
     public static final int AUTO_JOIN_TEMPORARY_DISABLED  = 1;
+    /** @hide
+     * network was temporary disabled due to bad connection, which cant be attributed
+     * to weak RSSI */
+    public static final int AUTO_JOIN_TEMPORARY_DISABLED_LINK_ERRORS  = 32;
+    /** @hide */
+    public static final int AUTO_JOIN_TEMPORARY_DISABLED_AT_SUPPLICANT  = 64;
     /** @hide */
     public static final int AUTO_JOIN_DISABLED_ON_AUTH_FAILURE  = 128;
     /** @hide */
@@ -476,6 +545,24 @@
      */
     public int autoJoinStatus;
 
+
+    /**
+     * @hide
+     */
+    public long blackListTimestamp;
+
+    /**
+     * @hide
+     * last time the system was connected to this configuration.
+     */
+    public long lastConnected;
+
+    /**
+     * @hide
+     * last time the system was disconnected to this configuration.
+     */
+    public long lastDisconnected;
+
     /**
      * Set if the configuration was self added by the framework
      * This boolean is cleared if we get a connect/save/ update or
@@ -586,7 +673,20 @@
 
         // TODO: Add more checks
         return true;
+    }
 
+    /**
+     * Helper function, identify if a configuration is linked
+     * @hide
+     */
+    public boolean isLinked(WifiConfiguration config) {
+        if (config.linkedConfigurations != null && linkedConfigurations != null) {
+            if (config.linkedConfigurations.get(configKey()) != null
+                    && linkedConfigurations.get(config.configKey()) != null) {
+                return true;
+            }
+        }
+        return  false;
     }
 
     /**
@@ -614,6 +714,17 @@
         return mostRecent;
     }
 
+    /** @hide **/
+    public void setAutoJoinStatus(int status) {
+        if (status < 0) status = 0;
+        if (status == 0) {
+            blackListTimestamp = 0;
+        }  else if (status > autoJoinStatus) {
+            blackListTimestamp = System.currentTimeMillis();
+        }
+        autoJoinStatus = status;
+    }
+
     @Override
     public String toString() {
         StringBuilder sbuf = new StringBuilder();
@@ -697,6 +808,15 @@
         if (selfAdded)  sbuf.append("selfAdded");
         if (creatorUid != 0)  sbuf.append("uid=" + Integer.toString(creatorUid));
 
+        if (blackListTimestamp != 0) {
+            long now_ms = System.currentTimeMillis();
+            long diff = now_ms - blackListTimestamp;
+            if (diff <= 0) {
+                sbuf.append("blackListed since <incorrect>");
+            } else {
+                sbuf.append("blackListed since ").append(Long.toString(diff/1000)).append( "sec");
+            }
+        }
 
         return sbuf.toString();
     }
@@ -987,6 +1107,9 @@
             lastUpdateUid = source.lastUpdateUid;
             creatorUid = source.creatorUid;
             peerWifiConfiguration = source.peerWifiConfiguration;
+            blackListTimestamp = source.blackListTimestamp;
+            lastConnected = source.lastConnected;
+            lastDisconnected = source.lastDisconnected;
         }
     }
 
@@ -1030,17 +1153,7 @@
         dest.writeInt(creatorUid);
         dest.writeInt(lastConnectUid);
         dest.writeInt(lastUpdateUid);
-        /*
-        TODO: should we write the cache results to the parcel?
-        if (scanResultCache != null) {
-            dest.writeInt(WifiConfiguration.SCAN_CACHE_TAG);
-            dest.writeInt(scanResultCache.size());
-            for (ScanResult result : scanResultCache.values()) {
-                result.writeToParcel(dest, flags);
-            }
-        } else {
-            dest.writeInt(WifiConfiguration.NOTHING_TAG);
-        }*/
+        dest.writeLong(blackListTimestamp);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -1079,26 +1192,7 @@
                 config.creatorUid = in.readInt();
                 config.lastConnectUid = in.readInt();
                 config.lastUpdateUid = in.readInt();
-                /*
-                TODO: should we write the cache results to the parcel?
-                boolean done = false;
-                do {
-                    int tag = in.readInt();
-                    switch (tag) {
-                        case WifiConfiguration.SCAN_CACHE_TAG:
-                            int size = in.readInt();
-                            config.scanResultCache = new HashMap<String, ScanResult>();
-                            while (size > 0) {
-                                ScanResult result = ScanResult.CREATOR.createFromParcel(in);
-                                config.scanResultCache.put(result.BSSID, result);
-                                size--;
-                            }
-                            break;
-                        case WifiConfiguration.NOTHING_TAG:
-                            done = true;
-                            break;
-                    }
-                } while (!done);*/
+                config.blackListTimestamp = in.readLong();
                 return config;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 1484d49..7debb93 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -224,9 +224,9 @@
         public static final int TTLS    = 2;
         /** EAP-Password */
         public static final int PWD     = 3;
-        /** EAP-Subscriber Identity Module */
+        /** EAP-Subscriber Identity Module {@hide} */
         public static final int SIM     = 4;
-        /** EAP-Authentication and Key Agreement */
+        /** EAP-Authentication and Key Agreement {@hide} */
         public static final int AKA     = 5;
         /** @hide */
         public static final String[] strings = { "PEAP", "TLS", "TTLS", "PWD", "SIM", "AKA" };
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index f44cb0a..6760c56 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -40,7 +40,7 @@
      * of <code>DetailedState</code>.
      */
     private static final EnumMap<SupplicantState, DetailedState> stateMap =
-        new EnumMap<SupplicantState, DetailedState>(SupplicantState.class);
+            new EnumMap<SupplicantState, DetailedState>(SupplicantState.class);
 
     static {
         stateMap.put(SupplicantState.DISCONNECTED, DetailedState.DISCONNECTED);
@@ -62,14 +62,31 @@
     private String mBSSID;
     private WifiSsid mWifiSsid;
     private int mNetworkId;
-    /** Received Signal Strength Indicator */
+
+    /** @hide **/
+    public static final int INVALID_RSSI = -127;
+
+    /** @hide **/
+    public static final int MIN_RSSI = -126;
+
+    /** @hide **/
+    public static final int MAX_RSSI = 200;
+
+
+    /**
+     * Received Signal Strength Indicator
+     */
     private int mRssi;
 
-    /** Link speed in Mbps */
+    /**
+     * Link speed in Mbps
+     */
     public static final String LINK_SPEED_UNITS = "Mbps";
     private int mLinkSpeed;
 
-    /** Frequency in MHz */
+    /**
+     * Frequency in MHz
+     */
     public static final String FREQUENCY_UNITS = "MHz";
     private int mFrequency;
 
@@ -77,9 +94,116 @@
     private String mMacAddress;
 
     /**
-     * Flag indicating that AP has hinted that upstream connection is metered,
-     * and sensitive to heavy data transfers.
+     * @hide
      */
+    public long txBad;
+    /**
+     * @hide
+     */
+    public long txRetries;
+    /**
+     * @hide
+     */
+    public long txSuccess;
+    /**
+     * @hide
+     */
+    public long rxSuccess;
+    /**
+     * @hide
+     */
+    public double txBadRate;
+    /**
+     * @hide
+     */
+    public double txRetriesRate;
+    /**
+     * @hide
+     */
+    public double txSuccessRate;
+    /**
+     * @hide
+     */
+    public double rxSuccessRate;
+
+    /**
+     * @hide
+     */
+    public int badRssiCount;
+
+    /**
+     * @hide
+     */
+    public int lowRssiCount;
+
+    /**
+     * @hide *
+     */
+    public int score;
+
+    /**
+     * TODO: get actual timestamp and calculate true rates
+     * @hide
+     */
+    public void updatePacketRates(WifiLinkLayerStats stats) {
+        if (stats != null) {
+            long txgood = stats.txmpdu_be + stats.txmpdu_bk + stats.txmpdu_vi + stats.txmpdu_vo;
+            long txretries = stats.retries_be + stats.retries_bk
+                    + stats.retries_vi + stats.retries_vo;
+            long rxgood = stats.rxmpdu_be + stats.rxmpdu_bk + stats.rxmpdu_vi + stats.rxmpdu_vo;
+            long txbad = stats.lostmpdu_be + stats.lostmpdu_bk
+                    + stats.lostmpdu_vi + stats.lostmpdu_vo;
+
+            txBadRate = (txBadRate * 0.5)
+                + ((double) (txbad - txBad) * 0.5);
+            txSuccessRate = (txSuccessRate * 0.5)
+                + ((double) (txgood - txSuccess) * 0.5);
+            rxSuccessRate = (rxSuccessRate * 0.5)
+                + ((double) (rxgood - rxSuccess) * 0.5);
+            txRetriesRate = (txRetriesRate * 0.5)
+                + ((double) (txretries - txRetries) * 0.5);
+
+            txBad = txbad;
+            txSuccess = txgood;
+            rxSuccess = rxgood;
+            txRetries = txretries;
+        } else {
+            txBad = 0;
+            txSuccess = 0;
+            rxSuccess = 0;
+            txRetries = 0;
+            txBadRate = 0;
+            txSuccessRate = 0;
+            rxSuccessRate = 0;
+            txRetriesRate = 0;
+        }
+    }
+
+
+    /**
+     * This function is less powerful and used if the WifiLinkLayerStats API is not implemented
+     * at the Wifi HAL
+     * @hide
+     */
+    public void updatePacketRates(long txPackets, long rxPackets) {
+        //paranoia
+        txBad = 0;
+        txRetries = 0;
+        txBadRate = 0;
+        txRetriesRate = 0;
+
+        txSuccessRate = (txSuccessRate * 0.5)
+                + ((double) (txPackets - txSuccess) * 0.5);
+        rxSuccessRate = (rxSuccessRate * 0.5)
+                + ((double) (rxPackets - rxSuccess) * 0.5);
+        txSuccess = txPackets;
+        rxSuccess = rxPackets;
+    }
+
+        /**
+         * Flag indicating that AP has hinted that upstream connection is metered,
+         * and sensitive to heavy data transfers.
+         */
     private boolean mMeteredHint;
 
     /** @hide */
@@ -88,9 +212,32 @@
         mBSSID = null;
         mNetworkId = -1;
         mSupplicantState = SupplicantState.UNINITIALIZED;
-        mRssi = -9999;
+        mRssi = INVALID_RSSI;
         mLinkSpeed = -1;
         mFrequency = -1;
+        txBad = 0;
+    }
+
+    /** @hide */
+    public void reset() {
+        setInetAddress(null);
+        setBSSID(null);
+        setSSID(null);
+        setNetworkId(-1);
+        setRssi(INVALID_RSSI);
+        setLinkSpeed(-1);
+        setFrequency(-1);
+        setMeteredHint(false);
+        txSuccess = 0;
+        rxSuccess = 0;
+        txRetries = 0;
+        txBadRate = 0;
+        txSuccessRate = 0;
+        rxSuccessRate = 0;
+        txRetriesRate = 0;
+        lowRssiCount = 0;
+        badRssiCount = 0;
+        score = 0;
     }
 
     /**
@@ -109,6 +256,17 @@
             mIpAddress = source.mIpAddress;
             mMacAddress = source.mMacAddress;
             mMeteredHint = source.mMeteredHint;
+            txBad = source.txBad;
+            txRetries = source.txRetries;
+            txSuccess = source.txSuccess;
+            rxSuccess = source.rxSuccess;
+            txBadRate = source.txBadRate;
+            txRetriesRate = source.txRetriesRate;
+            txSuccessRate = source.txSuccessRate;
+            rxSuccessRate = source.rxSuccessRate;
+            score = source.score;
+            badRssiCount = source.badRssiCount;
+            lowRssiCount = source.lowRssiCount;
         }
     }
 
@@ -158,7 +316,7 @@
     /**
      * Returns the received signal strength indicator of the current 802.11
      * network, in dBm.
-     * @return the RSSI, in the range -110 to 10
+     * @return the RSSI, in the range -127 to 200
      */
     public int getRssi() {
         return mRssi;
@@ -166,6 +324,10 @@
 
     /** @hide */
     public void setRssi(int rssi) {
+        if (rssi < INVALID_RSSI)
+            rssi = INVALID_RSSI;
+        if (rssi > MAX_RSSI)
+            rssi = MAX_RSSI;
         mRssi = rssi;
     }
 
@@ -198,6 +360,22 @@
     }
 
     /**
+     * @hide
+     * TODO: makes real freq boundaries
+     */
+    public boolean is24GHz() {
+        return mFrequency < 4000;
+    }
+
+    /**
+     * @hide
+     * TODO: makes real freq boundaries
+     */
+    public boolean is5GHz() {
+        return mFrequency > 4000;
+    }
+
+    /**
      * Record the MAC address of the WLAN interface
      * @param macAddress the MAC address in {@code XX:XX:XX:XX:XX:XX} form
      * @hide
@@ -325,8 +503,8 @@
             append(", Link speed: ").append(mLinkSpeed).append(LINK_SPEED_UNITS).
             append(", Frequency: ").append(mFrequency).append(FREQUENCY_UNITS).
             append(", Net ID: ").append(mNetworkId).
-            append(", Metered hint: ").append(mMeteredHint);
-
+            append(", Metered hint: ").append(mMeteredHint).
+            append(", score: ").append(Integer.toString(score));
         return sb.toString();
     }
 
@@ -356,6 +534,13 @@
         dest.writeString(mBSSID);
         dest.writeString(mMacAddress);
         dest.writeInt(mMeteredHint ? 1 : 0);
+        dest.writeInt(score);
+        dest.writeDouble(txSuccessRate);
+        dest.writeDouble(txRetriesRate);
+        dest.writeDouble(txBadRate);
+        dest.writeDouble(rxSuccessRate);
+        dest.writeInt(badRssiCount);
+        dest.writeInt(lowRssiCount);
         mSupplicantState.writeToParcel(dest, flags);
     }
 
@@ -379,6 +564,13 @@
                 info.mBSSID = in.readString();
                 info.mMacAddress = in.readString();
                 info.mMeteredHint = in.readInt() != 0;
+                info.score = in.readInt();
+                info.txSuccessRate = in.readDouble();
+                info.txRetriesRate = in.readDouble();
+                info.txBadRate = in.readDouble();
+                info.rxSuccessRate = in.readDouble();
+                info.badRssiCount = in.readInt();
+                info.lowRssiCount = in.readInt();
                 info.mSupplicantState = SupplicantState.CREATOR.createFromParcel(in);
                 return info;
             }
diff --git a/wifi/java/android/net/wifi/WifiLinkLayerStats.java b/wifi/java/android/net/wifi/WifiLinkLayerStats.java
index 922eddd..ae2fa98 100644
--- a/wifi/java/android/net/wifi/WifiLinkLayerStats.java
+++ b/wifi/java/android/net/wifi/WifiLinkLayerStats.java
@@ -111,6 +111,8 @@
     /** {@hide} */
     public String toString() {
         StringBuilder sbuf = new StringBuilder();
+        sbuf.append(" WifiLinkLayerStats: ").append('\n');
+
         if (this.SSID != null) {
             sbuf.append(" SSID: ").append(this.SSID).append('\n');
         }
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index bf46745..141a69e 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1413,14 +1413,12 @@
     /**
      * Passed with {@link ActionListener#onFailure}.
      * Indicates that the operation failed due to an internal error.
-     * @hide
      */
     public static final int ERROR                       = 0;
 
     /**
      * Passed with {@link ActionListener#onFailure}.
      * Indicates that the operation is already in progress
-     * @hide
      */
     public static final int IN_PROGRESS                 = 1;
 
@@ -1428,30 +1426,28 @@
      * Passed with {@link ActionListener#onFailure}.
      * Indicates that the operation failed because the framework is busy and
      * unable to service the request
-     * @hide
      */
     public static final int BUSY                        = 2;
 
     /* WPS specific errors */
-    /** WPS overlap detected {@hide} */
+    /** WPS overlap detected */
     public static final int WPS_OVERLAP_ERROR           = 3;
-    /** WEP on WPS is prohibited {@hide} */
+    /** WEP on WPS is prohibited */
     public static final int WPS_WEP_PROHIBITED          = 4;
-    /** TKIP only prohibited {@hide} */
+    /** TKIP only prohibited */
     public static final int WPS_TKIP_ONLY_PROHIBITED    = 5;
-    /** Authentication failure on WPS {@hide} */
+    /** Authentication failure on WPS */
     public static final int WPS_AUTH_FAILURE            = 6;
-    /** WPS timed out {@hide} */
+    /** WPS timed out */
     public static final int WPS_TIMED_OUT               = 7;
 
     /**
      * Passed with {@link ActionListener#onFailure}.
      * Indicates that the operation failed due to invalid inputs
-     * @hide
      */
     public static final int INVALID_ARGS                = 8;
 
-    /** Interface for callback invocation on an application action {@hide} */
+    /** Interface for callback invocation on an application action */
     public interface ActionListener {
         /** The operation succeeded */
         public void onSuccess();
@@ -1463,7 +1459,7 @@
         public void onFailure(int reason);
     }
 
-    /** Interface for callback invocation on a start WPS action {@hide} */
+    /** Interface for callback invocation on a start WPS action */
     public interface WpsListener {
         /** WPS start succeeded */
         public void onStartSuccess(String pin);
@@ -1745,7 +1741,6 @@
      * @param listener for callbacks on success or failure. Can be null.
      * @throws IllegalStateException if the WifiManager instance needs to be
      * initialized again
-     * @hide
      */
     public void startWps(WpsInfo config, WpsListener listener) {
         if (config == null) throw new IllegalArgumentException("config cannot be null");
@@ -1759,7 +1754,6 @@
      * @param listener for callbacks on success or failure. Can be null.
      * @throws IllegalStateException if the WifiManager instance needs to be
      * initialized again
-     * @hide
      */
     public void cancelWps(ActionListener listener) {
         validateChannel();
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 3b65ca8..21b700d 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -153,12 +153,17 @@
             dest.writeInt(band);
             dest.writeInt(periodInMs);
             dest.writeInt(reportEvents);
-            dest.writeInt(channels.length);
 
-            for (int i = 0; i < channels.length; i++) {
-                dest.writeInt(channels[i].frequency);
-                dest.writeInt(channels[i].dwellTimeMS);
-                dest.writeInt(channels[i].passive ? 1 : 0);
+            if (channels != null) {
+                dest.writeInt(channels.length);
+
+                for (int i = 0; i < channels.length; i++) {
+                    dest.writeInt(channels[i].frequency);
+                    dest.writeInt(channels[i].dwellTimeMS);
+                    dest.writeInt(channels[i].passive ? 1 : 0);
+                }
+            } else {
+                dest.writeInt(0);
             }
         }
 
@@ -211,10 +216,14 @@
 
         /** Implement the Parcelable interface {@hide} */
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(mResults.length);
-            for (int i = 0; i < mResults.length; i++) {
-                ScanResult result = mResults[i];
-                result.writeToParcel(dest, flags);
+            if (mResults != null) {
+                dest.writeInt(mResults.length);
+                for (int i = 0; i < mResults.length; i++) {
+                    ScanResult result = mResults[i];
+                    result.writeToParcel(dest, flags);
+                }
+            } else {
+                dest.writeInt(0);
             }
         }
 
@@ -324,13 +333,17 @@
             dest.writeInt(unchangedSampleSize);
             dest.writeInt(minApsBreachingThreshold);
             dest.writeInt(periodInMs);
-            dest.writeInt(hotspotInfos.length);
-            for (int i = 0; i < hotspotInfos.length; i++) {
-                HotspotInfo info = hotspotInfos[i];
-                dest.writeString(info.bssid);
-                dest.writeInt(info.low);
-                dest.writeInt(info.high);
-                dest.writeInt(info.frequencyHint);
+            if (hotspotInfos != null) {
+                dest.writeInt(hotspotInfos.length);
+                for (int i = 0; i < hotspotInfos.length; i++) {
+                    HotspotInfo info = hotspotInfos[i];
+                    dest.writeString(info.bssid);
+                    dest.writeInt(info.low);
+                    dest.writeInt(info.high);
+                    dest.writeInt(info.frequencyHint);
+                }
+            } else {
+                dest.writeInt(0);
             }
         }
 
@@ -456,13 +469,18 @@
         /** Implement the Parcelable interface {@hide} */
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(apLostThreshold);
-            dest.writeInt(hotspotInfos.length);
-            for (int i = 0; i < hotspotInfos.length; i++) {
-                HotspotInfo info = hotspotInfos[i];
-                dest.writeString(info.bssid);
-                dest.writeInt(info.low);
-                dest.writeInt(info.high);
-                dest.writeInt(info.frequencyHint);
+
+            if (hotspotInfos != null) {
+                dest.writeInt(hotspotInfos.length);
+                for (int i = 0; i < hotspotInfos.length; i++) {
+                    HotspotInfo info = hotspotInfos[i];
+                    dest.writeString(info.bssid);
+                    dest.writeInt(info.low);
+                    dest.writeInt(info.high);
+                    dest.writeInt(info.frequencyHint);
+                }
+            } else {
+                dest.writeInt(0);
             }
         }
 
@@ -680,6 +698,42 @@
         }
     }
 
+    /** @hide */
+    public static class OperationResult implements Parcelable {
+        public int reason;
+        public String description;
+
+        public OperationResult(int reason, String description) {
+            this.reason = reason;
+            this.description = description;
+        }
+
+        /** Implement the Parcelable interface {@hide} */
+        public int describeContents() {
+            return 0;
+        }
+
+        /** Implement the Parcelable interface {@hide} */
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(reason);
+            dest.writeString(description);
+        }
+
+        /** Implement the Parcelable interface {@hide} */
+        public static final Creator<OperationResult> CREATOR =
+                new Creator<OperationResult>() {
+                    public OperationResult createFromParcel(Parcel in) {
+                        int reason = in.readInt();
+                        String description = in.readString();
+                        return new OperationResult(reason, description);
+                    }
+
+                    public OperationResult[] newArray(int size) {
+                        return new OperationResult[size];
+                    }
+                };
+    }
+
     private static class ServiceHandler extends Handler {
         ServiceHandler(Looper looper) {
             super(looper);
@@ -717,9 +771,11 @@
                 case CMD_OP_SUCCEEDED :
                     ((ActionListener) listener).onSuccess();
                     break;
-                case CMD_OP_FAILED :
-                    ((ActionListener) listener).onFailure(msg.arg1, (String)msg.obj);
-                    removeListener(msg.arg2);
+                case CMD_OP_FAILED : {
+                        OperationResult result = (OperationResult)msg.obj;
+                        ((ActionListener) listener).onFailure(result.reason, result.description);
+                        removeListener(msg.arg2);
+                    }
                     break;
                 case CMD_SCAN_RESULT :
                     ((ScanListener) listener).onResults(
diff --git a/wifi/java/android/net/wifi/WpsInfo.java b/wifi/java/android/net/wifi/WpsInfo.java
index 2ad4ad0..ae2e771 100644
--- a/wifi/java/android/net/wifi/WpsInfo.java
+++ b/wifi/java/android/net/wifi/WpsInfo.java
@@ -40,7 +40,7 @@
     /** Wi-Fi Protected Setup. www.wi-fi.org/wifi-protected-setup has details */
     public int setup;
 
-    /** @hide */
+    /** Passed with pin method KEYPAD */
     public String BSSID;
 
     /** Passed with pin method configuration */
diff --git a/wifi/java/android/net/wifi/passpoint/IWifiPasspointManager.aidl b/wifi/java/android/net/wifi/passpoint/IWifiPasspointManager.aidl
index 8375d09..61c2b8a 100644
--- a/wifi/java/android/net/wifi/passpoint/IWifiPasspointManager.aidl
+++ b/wifi/java/android/net/wifi/passpoint/IWifiPasspointManager.aidl
@@ -16,6 +16,8 @@
 
 package android.net.wifi.passpoint;
 
+import android.net.wifi.ScanResult;
+import android.net.wifi.passpoint.WifiPasspointPolicy;
 import android.os.Messenger;
 
 /**
@@ -27,5 +29,6 @@
 {
     Messenger getMessenger();
     int getPasspointState();
+    List<WifiPasspointPolicy> requestCredentialMatch(in List<ScanResult> requested);
 }
 
diff --git a/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java b/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java
index 0769a64..54ac71e 100644
--- a/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java
+++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointCredential.java
@@ -20,10 +20,13 @@
 import android.os.Parcelable;
 import android.os.Parcel;
 
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Set;
+import java.util.List;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
+
 
 /**
  * A class representing a Wi-Fi Passpoint credential.
@@ -32,77 +35,90 @@
 public class WifiPasspointCredential implements Parcelable {
 
     private final static String TAG = "PasspointCredential";
-    private String mWifiSPFQDN;
+    private final static boolean DBG = true;
+
+    /** Wi-Fi nodes**/
+    private String mWifiSpFqdn;
+
+    /** PerProviderSubscription nodes **/
     private String mCredentialName;
-    private String mUpdateIdentifier;
+
+    /** SubscriptionUpdate nodes **/
+    private String mSubscriptionUpdateInterval;
     private String mSubscriptionUpdateMethod;
+    private String mSubscriptionUpdateRestriction;
+    private String mSubscriptionUpdateURI;
+    private String mSubscriptionUpdateUsername;
+    private String mSubscriptionUpdatePassword;
+
+    /** HomeSP nodes **/
+    private String mHomeSpFqdn;
+    private String mFriendlyName;
+    private Collection<WifiPasspointDmTree.HomeOIList> mHomeOIList;
+    private Collection<WifiPasspointDmTree.OtherHomePartners> mOtherHomePartnerList;
+
+    /** SubscriptionParameters nodes**/
+    private String mCreationDate;
+    private String mExpirationDate;
+
+    /** Credential nodes **/
     private String mType;
     private String mInnerMethod;
     private String mCertType;
     private String mCertSha256Fingerprint;
+    private String mUpdateIdentifier;
     private String mUsername;
     private String mPasswd;
+    private String mRealm;
     private String mImsi;
     private String mMcc;
     private String mMnc;
     private String mCaRootCert;
-    private String mRealm;
-    private int mPriority; //User preferred priority; The smaller, the higher
-    private boolean mUserPreferred = false;
-    private String mHomeSpFqdn;
-    private String mFriendlyName;
-    private String mOtherhomepartnerFqdn;
     private String mClientCert;
-    private String mCreationDate;
-    private String mExpirationDate;
-
-    private String mSubscriptionDMAccUsername;
-    private String mSubscriptionDMAccPassword;
-    private String mSubscriptionUpdateInterval;
-
-    private String mPolicyUpdateURI;
-    private String mPolicyUpdateInterval;
-    private String mPolicyDMAccUsername;
-    private String mPolicyDMAccPassword;
-    private String mPolicyUpdateRestriction;
-    private String mPolicyUpdateMethod;
-
-    private Collection<WifiPasspointDmTree.PreferredRoamingPartnerList> mPreferredRoamingPartnerList;
-    private Collection<WifiPasspointDmTree.HomeOIList> mHomeOIList;
-    private Collection<WifiPasspointDmTree.MinBackhaulThresholdNetwork> mMinBackhaulThresholdNetwork;
-    private Collection<WifiPasspointDmTree.RequiredProtoPortTuple> mRequiredProtoPortTuple;
-    private Collection<WifiPasspointDmTree.SPExclusionList> mSpExclusionList;
-    private String mMaxBssLoad;
-
-    private boolean mIsMachineRemediation;
-
-    private String mAAACertURL;
-    private String mAAASha256Fingerprint;
-
-    private String mSubscriptionUpdateRestriction;
-    private String mSubscriptionUpdateURI;
-
     private boolean mCheckAaaServerCertStatus;
 
-    /** @hide */
-    public WifiPasspointCredential() {
+    /** Policy nodes **/
+    private String mPolicyUpdateUri;
+    private String mPolicyUpdateInterval;
+    private String mPolicyUpdateUsername;
+    private String mPolicyUpdatePassword;
+    private String mPolicyUpdateRestriction;
+    private String mPolicyUpdateMethod;
+    private Collection<WifiPasspointDmTree.PreferredRoamingPartnerList> mPreferredRoamingPartnerList;
+    private Collection<WifiPasspointDmTree.MinBackhaulThresholdNetwork> mMinBackhaulThresholdNetwork;
+    private Collection<WifiPasspointDmTree.SPExclusionList> mSpExclusionList;
+    private Collection<WifiPasspointDmTree.RequiredProtoPortTuple> mRequiredProtoPortTuple;
+    private String mMaxBssLoad;
 
-    }
+    /** CrednetialPriority node **/
+    private int mCrednetialPriority;
+
+    /** AAAServerTrustRoot nodes **/
+    private String mAaaCertUrl;
+    private String mAaaSha256Fingerprint;
+
+    /** Others **/
+    private boolean mIsMachineRemediation;
+    private boolean mUserPreferred = false;
+    private String mWifiTreePath;
+    private WifiEnterpriseConfig mEnterpriseConfig;
+
+    /** @hide */
+    public WifiPasspointCredential() {}
 
     /**
      * Constructor
      * @param realm Realm of the passpoint credential
-     * @param config Credential information, must be either EAP-TLS or EAP-TTLS.
+     * @param fqdn Fully qualified domain name (FQDN) of the credential
+     * @param config Enterprise config, must be either EAP-TLS or EAP-TTLS
      * @see WifiEnterpriseConfig
      */
-    public WifiPasspointCredential(String realm, WifiEnterpriseConfig config) {
+    public WifiPasspointCredential(String realm, String fqdn, WifiEnterpriseConfig config) {
         mRealm = realm;
         switch (config.getEapMethod()) {
             case WifiEnterpriseConfig.Eap.TLS:
-                // TODO;
-                break;
             case WifiEnterpriseConfig.Eap.TTLS:
-                // TODO;
+                mEnterpriseConfig = new WifiEnterpriseConfig(config);
                 break;
             default:
                 // ignore
@@ -113,84 +129,6 @@
     public WifiPasspointCredential(String type,
             String caroot,
             String clientcert,
-            WifiPasspointDmTree.SpFqdn sp,
-            WifiPasspointDmTree.CredentialInfo credinfo) {
-
-        if (credinfo == null) {
-            return;
-        }
-
-        mType = type;
-        mCaRootCert = caroot;
-        mClientCert = clientcert;
-
-        mWifiSPFQDN = sp.nodeName;
-        mUpdateIdentifier = sp.perProviderSubscription.UpdateIdentifier;
-
-        mCredentialName = credinfo.nodeName;
-        Set set = credinfo.homeSP.otherHomePartners.entrySet();
-        Iterator i = set.iterator();
-        if (i.hasNext()) {
-            Map.Entry entry3 = (Map.Entry) i.next();
-            WifiPasspointDmTree.OtherHomePartners ohp = (WifiPasspointDmTree.OtherHomePartners) entry3.getValue();
-            mOtherhomepartnerFqdn = ohp.FQDN;
-        }
-
-        set = credinfo.aAAServerTrustRoot.entrySet();
-        i = set.iterator();
-        if (i.hasNext()) {
-            Map.Entry entry3 = (Map.Entry) i.next();
-            WifiPasspointDmTree.AAAServerTrustRoot aaa = (WifiPasspointDmTree.AAAServerTrustRoot) entry3.getValue();
-            mAAACertURL = aaa.CertURL;
-            mAAASha256Fingerprint = aaa.CertSHA256Fingerprint;
-        }
-
-        mCertType = credinfo.credential.digitalCertificate.CertificateType;
-        mCertSha256Fingerprint = credinfo.credential.digitalCertificate.CertSHA256Fingerprint;
-        mUsername = credinfo.credential.usernamePassword.Username;
-        mPasswd = credinfo.credential.usernamePassword.Password;
-        mIsMachineRemediation = credinfo.credential.usernamePassword.MachineManaged;
-        mInnerMethod = credinfo.credential.usernamePassword.eAPMethod.InnerMethod;
-        mImsi = credinfo.credential.sim.IMSI;
-        mCreationDate = credinfo.credential.CreationDate;
-        mExpirationDate = credinfo.credential.ExpirationDate;
-        mRealm = credinfo.credential.Realm;
-
-        if (credinfo.credentialPriority == null) {
-            credinfo.credentialPriority = "128";
-        }
-        mPriority = Integer.parseInt(credinfo.credentialPriority);
-
-        mHomeSpFqdn = credinfo.homeSP.FQDN;
-
-        mSubscriptionUpdateInterval = credinfo.subscriptionUpdate.UpdateInterval;
-        mSubscriptionUpdateMethod = credinfo.subscriptionUpdate.UpdateMethod;
-        mSubscriptionUpdateRestriction = credinfo.subscriptionUpdate.Restriction;
-        mSubscriptionUpdateURI = credinfo.subscriptionUpdate.URI;
-        mSubscriptionDMAccUsername = credinfo.subscriptionUpdate.usernamePassword.Username;
-        mSubscriptionDMAccPassword = credinfo.subscriptionUpdate.usernamePassword.Password;
-
-        mPolicyUpdateURI = credinfo.policy.policyUpdate.URI;
-        mPolicyUpdateInterval = credinfo.policy.policyUpdate.UpdateInterval;
-        mPolicyDMAccUsername = credinfo.policy.policyUpdate.usernamePassword.Username;
-        mPolicyDMAccPassword = credinfo.policy.policyUpdate.usernamePassword.Password;
-        mPolicyUpdateRestriction = credinfo.policy.policyUpdate.Restriction;
-        mPolicyUpdateMethod = credinfo.policy.policyUpdate.UpdateMethod;
-        mPreferredRoamingPartnerList = credinfo.policy.preferredRoamingPartnerList.values();
-        mMinBackhaulThresholdNetwork = credinfo.policy.minBackhaulThreshold.values();
-        mRequiredProtoPortTuple = credinfo.policy.requiredProtoPortTuple.values();
-        mMaxBssLoad = credinfo.policy.maximumBSSLoadValue;
-        mSpExclusionList = credinfo.policy.sPExclusionList.values();
-
-        mHomeOIList = credinfo.homeSP.homeOIList.values();
-        mFriendlyName = credinfo.homeSP.FriendlyName;
-        mCheckAaaServerCertStatus = credinfo.credential.CheckAAAServerCertStatus;
-    }
-
-    /** @hide */
-    public WifiPasspointCredential(String type,
-            String caroot,
-            String clientcert,
             String mcc,
             String mnc,
             WifiPasspointDmTree.SpFqdn sp,
@@ -204,25 +142,19 @@
         mCaRootCert = caroot;
         mClientCert = clientcert;
 
-        mWifiSPFQDN = sp.nodeName;
+        mWifiSpFqdn = sp.nodeName;
         mUpdateIdentifier = sp.perProviderSubscription.UpdateIdentifier;
 
         mCredentialName = credinfo.nodeName;
-        Set set = credinfo.homeSP.otherHomePartners.entrySet();
+        mOtherHomePartnerList = credinfo.homeSP.otherHomePartners.values();
+
+        Set set = credinfo.aAAServerTrustRoot.entrySet();
         Iterator i = set.iterator();
         if (i.hasNext()) {
             Map.Entry entry3 = (Map.Entry) i.next();
-            WifiPasspointDmTree.OtherHomePartners ohp = (WifiPasspointDmTree.OtherHomePartners) entry3.getValue();
-            mOtherhomepartnerFqdn = ohp.FQDN;
-        }
-
-        set = credinfo.aAAServerTrustRoot.entrySet();
-        i = set.iterator();
-        if (i.hasNext()) {
-            Map.Entry entry3 = (Map.Entry) i.next();
             WifiPasspointDmTree.AAAServerTrustRoot aaa = (WifiPasspointDmTree.AAAServerTrustRoot) entry3.getValue();
-            mAAACertURL = aaa.CertURL;
-            mAAASha256Fingerprint = aaa.CertSHA256Fingerprint;
+            mAaaCertUrl = aaa.CertURL;
+            mAaaSha256Fingerprint = aaa.CertSHA256Fingerprint;
         }
 
         mCertType = credinfo.credential.digitalCertificate.CertificateType;
@@ -239,22 +171,24 @@
         mRealm = credinfo.credential.Realm;
 
         if (credinfo.credentialPriority == null) {
-            credinfo.credentialPriority = "128";
+            mCrednetialPriority = 128;
+        } else {
+            mCrednetialPriority = Integer.parseInt(credinfo.credentialPriority);
         }
-        mPriority = Integer.parseInt(credinfo.credentialPriority);
 
         mHomeSpFqdn = credinfo.homeSP.FQDN;
 
+        mSubscriptionUpdateInterval = credinfo.subscriptionUpdate.UpdateInterval;
         mSubscriptionUpdateMethod = credinfo.subscriptionUpdate.UpdateMethod;
         mSubscriptionUpdateRestriction = credinfo.subscriptionUpdate.Restriction;
         mSubscriptionUpdateURI = credinfo.subscriptionUpdate.URI;
-        mSubscriptionDMAccUsername = credinfo.subscriptionUpdate.usernamePassword.Username;
-        mSubscriptionDMAccPassword = credinfo.subscriptionUpdate.usernamePassword.Password;
+        mSubscriptionUpdateUsername = credinfo.subscriptionUpdate.usernamePassword.Username;
+        mSubscriptionUpdatePassword = credinfo.subscriptionUpdate.usernamePassword.Password;
 
-        mPolicyUpdateURI = credinfo.policy.policyUpdate.URI;
+        mPolicyUpdateUri = credinfo.policy.policyUpdate.URI;
         mPolicyUpdateInterval = credinfo.policy.policyUpdate.UpdateInterval;
-        mPolicyDMAccUsername = credinfo.policy.policyUpdate.usernamePassword.Username;
-        mPolicyDMAccPassword = credinfo.policy.policyUpdate.usernamePassword.Password;
+        mPolicyUpdateUsername = credinfo.policy.policyUpdate.usernamePassword.Username;
+        mPolicyUpdatePassword = credinfo.policy.policyUpdate.usernamePassword.Password;
         mPolicyUpdateRestriction = credinfo.policy.policyUpdate.Restriction;
         mPolicyUpdateMethod = credinfo.policy.policyUpdate.UpdateMethod;
         mPreferredRoamingPartnerList = credinfo.policy.preferredRoamingPartnerList.values();
@@ -265,6 +199,7 @@
 
         mHomeOIList = credinfo.homeSP.homeOIList.values();
         mFriendlyName = credinfo.homeSP.FriendlyName;
+        mCheckAaaServerCertStatus = credinfo.credential.CheckAAAServerCertStatus;
     }
 
     /** @hide */
@@ -283,8 +218,8 @@
     }
 
     /** @hide */
-    public String getWifiSPFQDN() {
-        return mWifiSPFQDN;
+    public String getWifiSpFqdn() {
+        return mWifiSpFqdn;
     }
 
     /** @hide */
@@ -293,16 +228,26 @@
     }
 
     /** @hide */
-    public String getEapMethodStr() {
+    public String getType() {
         return mType;
     }
 
     /**
-     * Get EAP method of this Passpoint credential.
-     * @return EAP method, refer to {@link WifiEnterpriseConfig.Eap} for possible return values
+     * Get enterprise config of this Passpoint credential.
+     * @return Enterprise config
+     * @see WifiEnterpriseConfig
      */
-    public int getEapMethod() {
-        return 0;
+    public WifiEnterpriseConfig getEnterpriseConfig() {
+        return new WifiEnterpriseConfig(mEnterpriseConfig);
+    }
+
+    /**
+     * Set enterprise config of this Passpoint credential.
+     * @param config Enterprise config, must be either EAP-TLS or EAP-TTLS
+     * @see WifiEnterpriseConfig
+     */
+    public void setEnterpriseConfig(WifiEnterpriseConfig config) {
+        // TODO
     }
 
     /** @hide */
@@ -315,10 +260,7 @@
         return mCertSha256Fingerprint;
     }
 
-    /**
-     * Get the user name of this Passpoint credential, for EAP-TTLS only.
-     * @return user name
-     */
+    /** @hide */
     public String getUserName() {
         return mUsername;
     }
@@ -329,10 +271,7 @@
         return mPasswd;
     }
 
-    /**
-     * Get the IMSI of this Passpoint credential, for EAP-SIM / EAP-AKA only.
-     * @return IMSI
-     */
+    /** @hide */
     public String getImsi() {
         return mImsi;
     }
@@ -348,62 +287,75 @@
     }
 
     /** @hide */
-    public String getCaRootCert() {
+    public String getCaRootCertPath() {
         return mCaRootCert;
     }
 
-    /**
-     * Get the client certificate path of this Passpoint credential, for EAP-TLS only.
-     * @return client certificate path
-     */
+    /** @hide */
     public String getClientCertPath() {
         return mClientCert;
     }
 
     /**
-     * Get the realm of this Passpoint credential, for all EAP methods.
+     * Get the realm of this Passpoint credential.
      * @return Realm
      */
     public String getRealm() {
         return mRealm;
     }
 
+    /**
+     * Set the ream of this Passpoint credential.
+     * @param realm Realm
+     */
+    public void setRealm(String realm) {
+        mRealm = realm;
+    }
+
     /** @hide */
     public int getPriority() {
         if (mUserPreferred) {
             return 0;
         }
 
-        return mPriority;
+        return mCrednetialPriority;
     }
 
     /**
-     * Get the fully qualified domain name (FQDN) of this Passpoint credential,
-     * for all EAP methods.
+     * Get the fully qualified domain name (FQDN) of this Passpoint credential.
      * @return FQDN
      */
-    public String getFqdn() {
+    public String getHomeSpFqdn() {
         return mHomeSpFqdn;
     }
 
+    /**
+     * Set the fully qualified domain name (FQDN) of this Passpoint credential.
+     * @param fqdn FQDN
+     */
+    public void setFqdn(String fqdn) {
+        mHomeSpFqdn = fqdn;
+    }
+
+
     /** @hide */
-    public String getOtherhomepartners() {
-        return mOtherhomepartnerFqdn;
+    public Collection<WifiPasspointDmTree.OtherHomePartners> getOtherHomePartnerList() {
+        return mOtherHomePartnerList;
     }
 
     /** @hide */
-    public String getSubscriptionDMAccUsername() {
-        return mSubscriptionDMAccUsername;
+    public String getSubscriptionUpdateUsername() {
+        return mSubscriptionUpdateUsername;
     }
 
     /** @hide */
-    public String getSubscriptionDMAccPassword() {
-        return mSubscriptionDMAccPassword;
+    public String getSubscriptionUpdatePassword() {
+        return mSubscriptionUpdatePassword;
     }
 
     /** @hide */
-    public String getPolicyUpdateURI() {
-        return mPolicyUpdateURI;
+    public String getPolicyUpdateUri() {
+        return mPolicyUpdateUri;
     }
 
     /** @hide */
@@ -412,13 +364,13 @@
     }
 
     /** @hide */
-    public String getPolicyDMAccUsername() {
-        return mPolicyDMAccUsername;
+    public String getPolicyUpdateUsername() {
+        return mPolicyUpdateUsername;
     }
 
     /** @hide */
-    public String getPolicyDMAccPassword() {
-        return mPolicyDMAccPassword;
+    public String getPolicyUpdatePassword() {
+        return mPolicyUpdatePassword;
     }
 
     /** @hide */
@@ -447,12 +399,12 @@
     }
 
     /** @hide */
-    public Collection<WifiPasspointDmTree.PreferredRoamingPartnerList> getPrpList() {
+    public Collection<WifiPasspointDmTree.PreferredRoamingPartnerList> getPreferredRoamingPartnerList() {
         return mPreferredRoamingPartnerList;
     }
 
     /** @hide */
-    public Collection<WifiPasspointDmTree.HomeOIList> getHomeOIList() {
+    public Collection<WifiPasspointDmTree.HomeOIList> getHomeOiList() {
         return mHomeOIList;
     }
 
@@ -477,13 +429,13 @@
     }
 
     /** @hide */
-    public String getAAACertURL() {
-        return mAAACertURL;
+    public String getAaaCertUrl() {
+        return mAaaCertUrl;
     }
 
     /** @hide */
-    public String getAAASha256Fingerprint() {
-        return mAAASha256Fingerprint;
+    public String getAaaSha256Fingerprint() {
+        return mAaaSha256Fingerprint;
     }
 
     /** @hide */
@@ -560,72 +512,75 @@
         StringBuffer sb = new StringBuffer();
         String none = "<none>";
 
-        sb.append(", UpdateIdentifier: ")
-                .append(mUpdateIdentifier == null ? none : mUpdateIdentifier).
-                append(", SubscriptionUpdateMethod: ")
-                .append(mSubscriptionUpdateMethod == null ? none : mSubscriptionUpdateMethod).
-                append(", Type: ").append(mType == null ? none : mType).
-                append(", Username: ").append(mUsername == null ? none : mUsername).
-                append(", Passwd: ").append(mPasswd == null ? none : mPasswd).
-                append(", SubDMAccUsername: ")
-                .append(mSubscriptionDMAccUsername == null ? none : mSubscriptionDMAccUsername).
-                append(", SubDMAccPassword: ")
-                .append(mSubscriptionDMAccPassword == null ? none : mSubscriptionDMAccPassword).
-                append(", PolDMAccUsername: ")
-                .append(mPolicyDMAccUsername == null ? none : mPolicyDMAccUsername).
-                append(", PolDMAccPassword: ")
-                .append(mPolicyDMAccPassword == null ? none : mPolicyDMAccPassword).
-                append(", Imsi: ").append(mImsi == null ? none : mImsi).
-                append(", Mcc: ").append(mMcc == null ? none : mMcc).
-                append(", Mnc: ").append(mMnc == null ? none : mMnc).
-                append(", CaRootCert: ").append(mCaRootCert == null ? none : mCaRootCert).
-                append(", Realm: ").append(mRealm == null ? none : mRealm).
-                append(", Priority: ").append(mPriority).
-                append(", Fqdn: ").append(mHomeSpFqdn == null ? none : mHomeSpFqdn).
-                append(", Otherhomepartners: ")
-                .append(mOtherhomepartnerFqdn == null ? none : mOtherhomepartnerFqdn).
-                append(", ExpirationDate: ")
-                .append(mExpirationDate == null ? none : mExpirationDate).
-                append(", MaxBssLoad: ").append(mMaxBssLoad == null ? none : mMaxBssLoad).
-                append(", SPExclusionList: ").append(mSpExclusionList);
+        if (!DBG) {
+            sb.append(none);
+        } else {
+            sb.append(", UpdateIdentifier: ")
+            .append(mUpdateIdentifier == null ? none : mUpdateIdentifier)
+            .append(", SubscriptionUpdateMethod: ")
+            .append(mSubscriptionUpdateMethod == null ? none : mSubscriptionUpdateMethod)
+            .append(", Type: ").append(mType == null ? none : mType)
+            .append(", Username: ").append(mUsername == null ? none : mUsername)
+            .append(", Passwd: ").append(mPasswd == null ? none : mPasswd)
+            .append(", SubDMAccUsername: ")
+            .append(mSubscriptionUpdateUsername == null ? none : mSubscriptionUpdateUsername)
+            .append(", SubDMAccPassword: ")
+            .append(mSubscriptionUpdatePassword == null ? none : mSubscriptionUpdatePassword)
+            .append(", PolDMAccUsername: ")
+            .append(mPolicyUpdateUsername == null ? none : mPolicyUpdateUsername)
+            .append(", PolDMAccPassword: ")
+            .append(mPolicyUpdatePassword == null ? none : mPolicyUpdatePassword)
+            .append(", Imsi: ").append(mImsi == null ? none : mImsi)
+            .append(", Mcc: ").append(mMcc == null ? none : mMcc)
+            .append(", Mnc: ").append(mMnc == null ? none : mMnc)
+            .append(", CaRootCert: ").append(mCaRootCert == null ? none : mCaRootCert)
+            .append(", Realm: ").append(mRealm == null ? none : mRealm)
+            .append(", Priority: ").append(mCrednetialPriority)
+            .append(", Fqdn: ").append(mHomeSpFqdn == null ? none : mHomeSpFqdn)
+            .append(", Otherhomepartners: ")
+            .append(mOtherHomePartnerList == null ? none : mOtherHomePartnerList)
+            .append(", ExpirationDate: ")
+            .append(mExpirationDate == null ? none : mExpirationDate)
+            .append(", MaxBssLoad: ").append(mMaxBssLoad == null ? none : mMaxBssLoad)
+            .append(", SPExclusionList: ").append(mSpExclusionList);
 
-        if (mPreferredRoamingPartnerList != null) {
-            sb.append("PreferredRoamingPartnerList:");
-            for (WifiPasspointDmTree.PreferredRoamingPartnerList prpListItem : mPreferredRoamingPartnerList) {
-                sb.append("[fqdnmatch:").append(prpListItem.FQDN_Match).
-                        append(", priority:").append(prpListItem.Priority).
-                        append(", country:").append(prpListItem.Country).append("]");
+            if (mPreferredRoamingPartnerList != null) {
+                sb.append("PreferredRoamingPartnerList:");
+                for (WifiPasspointDmTree.PreferredRoamingPartnerList prpListItem : mPreferredRoamingPartnerList) {
+                    sb.append("[fqdnmatch:").append(prpListItem.FQDN_Match).
+                            append(", priority:").append(prpListItem.Priority).
+                            append(", country:").append(prpListItem.Country).append("]");
+                }
+            }
+
+            if (mHomeOIList != null) {
+                sb.append("HomeOIList:");
+                for (WifiPasspointDmTree.HomeOIList HomeOIListItem : mHomeOIList) {
+                    sb.append("[HomeOI:").append(HomeOIListItem.HomeOI).
+                            append(", HomeOIRequired:").append(HomeOIListItem.HomeOIRequired).
+                            append("]");
+                }
+            }
+
+            if (mMinBackhaulThresholdNetwork != null) {
+                sb.append("BackHaulThreshold:");
+                for (WifiPasspointDmTree.MinBackhaulThresholdNetwork BhtListItem : mMinBackhaulThresholdNetwork) {
+                    sb.append("[networkType:").append(BhtListItem.NetworkType).
+                            append(", dlBandwidth:").append(BhtListItem.DLBandwidth).
+                            append(", ulBandwidth:").append(BhtListItem.ULBandwidth).
+                            append("]");
+                }
+            }
+
+            if (mRequiredProtoPortTuple != null) {
+                sb.append("WifiMORequiredProtoPortTupleList:");
+                for (WifiPasspointDmTree.RequiredProtoPortTuple RpptListItem : mRequiredProtoPortTuple) {
+                    sb.append("[IPProtocol:").append(RpptListItem.IPProtocol).
+                            append(", PortNumber:").append(RpptListItem.PortNumber).
+                            append("]");
+                }
             }
         }
-
-        if (mHomeOIList != null) {
-            sb.append("HomeOIList:");
-            for (WifiPasspointDmTree.HomeOIList HomeOIListItem : mHomeOIList) {
-                sb.append("[HomeOI:").append(HomeOIListItem.HomeOI).
-                        append(", HomeOIRequired:").append(HomeOIListItem.HomeOIRequired).
-                        append("]");
-            }
-        }
-
-        if (mMinBackhaulThresholdNetwork != null) {
-            sb.append("BackHaulThreshold:");
-            for (WifiPasspointDmTree.MinBackhaulThresholdNetwork BhtListItem : mMinBackhaulThresholdNetwork) {
-                sb.append("[networkType:").append(BhtListItem.NetworkType).
-                        append(", dlBandwidth:").append(BhtListItem.DLBandwidth).
-                        append(", ulBandwidth:").append(BhtListItem.ULBandwidth).
-                        append("]");
-            }
-        }
-
-        if (mRequiredProtoPortTuple != null) {
-            sb.append("WifiMORequiredProtoPortTupleList:");
-            for (WifiPasspointDmTree.RequiredProtoPortTuple RpptListItem : mRequiredProtoPortTuple) {
-                sb.append("[IPProtocol:").append(RpptListItem.IPProtocol).
-                        append(", PortNumber:").append(RpptListItem.PortNumber).
-                        append("]");
-            }
-        }
-
         return sb.toString();
     }
 
@@ -636,19 +591,22 @@
 
     /** Implement the Parcelable interface {@hide} */
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mWifiSpFqdn);
+        dest.writeString(mCredentialName);
         dest.writeString(mType);
-        dest.writeString(mUsername);
-        dest.writeString(mPasswd);
-        dest.writeString(mImsi);
-        dest.writeString(mMcc);
-        dest.writeString(mMnc);
-        dest.writeString(mCaRootCert);
-        dest.writeString(mRealm);
-        dest.writeInt(mPriority);
+        dest.writeInt(mCrednetialPriority);
         dest.writeString(mHomeSpFqdn);
-        dest.writeString(mOtherhomepartnerFqdn);
-        dest.writeString(mClientCert);
-        dest.writeString(mExpirationDate);
+        dest.writeString(mRealm);
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public void readFromParcel(Parcel in) {
+        mWifiSpFqdn = in.readString();
+        mCredentialName = in.readString();
+        mType = in.readString();
+        mCrednetialPriority = in.readInt();
+        mHomeSpFqdn = in.readString();
+        mRealm = in.readString();
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -656,19 +614,12 @@
             new Creator<WifiPasspointCredential>() {
                 public WifiPasspointCredential createFromParcel(Parcel in) {
                     WifiPasspointCredential pc = new WifiPasspointCredential();
+                    pc.mWifiSpFqdn = in.readString();
+                    pc.mCredentialName = in.readString();
                     pc.mType = in.readString();
-                    pc.mUsername = in.readString();
-                    pc.mPasswd = in.readString();
-                    pc.mImsi = in.readString();
-                    pc.mMcc = in.readString();
-                    pc.mMnc = in.readString();
-                    pc.mCaRootCert = in.readString();
-                    pc.mRealm = in.readString();
-                    pc.mPriority = in.readInt();
+                    pc.mCrednetialPriority = in.readInt();
                     pc.mHomeSpFqdn = in.readString();
-                    pc.mOtherhomepartnerFqdn = in.readString();
-                    pc.mClientCert = in.readString();
-                    pc.mExpirationDate = in.readString();
+                    pc.mRealm = in.readString();
                     return pc;
                 }
 
@@ -681,9 +632,9 @@
     public int compareTo(WifiPasspointCredential another) {
 
         //The smaller the higher
-        if (mPriority < another.mPriority) {
+        if (mCrednetialPriority < another.mCrednetialPriority) {
             return -1;
-        } else if (mPriority == another.mPriority) {
+        } else if (mCrednetialPriority == another.mCrednetialPriority) {
             return this.mType.compareTo(another.mType);
         } else {
             return 1;
diff --git a/wifi/java/android/net/wifi/passpoint/WifiPasspointDmTree.java b/wifi/java/android/net/wifi/passpoint/WifiPasspointDmTree.java
index 9ff1973..bbf5fc6 100644
--- a/wifi/java/android/net/wifi/passpoint/WifiPasspointDmTree.java
+++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointDmTree.java
@@ -25,19 +25,17 @@
 /**
  * Required Mobile Device Management Tree Structure
  *
- *                           +----------+
- *                           | ./(Root) |
- *                           +----+-----+
- *                                |
- *          +---------+           |         +---------+   +---------+
- *          | DevInfo |-----------+---------| Wi-Fi   |---|SP FQDN* |
- *          +---------+           |         +---------+   +---------+
- *          +---------+           |
- *          |DevDetail|-----------+
- *          +---------+
- *
- * For example,
- * ./Wi-Fi/wi-fi.org/PerproviderSubscription/Cred01/Policy/PreferredRoamingPartnerList/Roa01/FQDN_Math
+ *                   +----------+
+ *                   | ./(Root) |
+ *                   +----+-----+
+ *                        |
+ *  +---------+           |         +---------+  +---------+
+ *  | DevInfo |-----------+---------|  Wi-Fi  |--|SP FQDN* |
+ *  +---------+           |         +---------+  +---------+
+ *  +---------+           |                           |
+ *  |DevDetail|-----------+                      +-----------------------+
+ *  +---------+                                  |PerproviderSubscription|--<X>+
+ *                                               +-----------------------+
  *
  * This class contains all nodes start from Wi-Fi
  * @hide
diff --git a/wifi/java/android/net/wifi/passpoint/WifiPasspointInfo.java b/wifi/java/android/net/wifi/passpoint/WifiPasspointInfo.java
index 99bea2f..aec8797 100644
--- a/wifi/java/android/net/wifi/passpoint/WifiPasspointInfo.java
+++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointInfo.java
@@ -88,68 +88,160 @@
                     CONNECTION_CAPABILITY |
                     OSU_PROVIDER;
 
-    /** TODO doc */
-    public String bssid;
 
-    /** TODO doc */
-    public String venueName;
+    public static class WanMetrics {
+        public static final int STATUS_RESERVED = 0;
+        public static final int STATUS_UP = 1;
+        public static final int STATUS_DOWN = 2;
+        public static final int STATUS_TEST = 3;
 
-    /** TODO doc */
-    public String networkAuthType;
+        public int wanInfo;
+        public long downlinkSpeed;
+        public long uplinkSpeed;
+        public int downlinkLoad;
+        public int uplinkLoad;
+        public int lmd;
 
-    /** TODO doc */
-    public String roamingConsortium;
+        public int getLinkStatus() {
+            return wanInfo & 0x3;
+        }
 
-    /** TODO doc */
-    public String ipAddrTypeAvaibility;
+        public boolean getSymmetricLink() {
+            return (wanInfo & (1 << 2)) != 0;
+        }
 
-    /** TODO doc */
-    public String naiRealm;
+        public boolean getAtCapacity() {
+            return (wanInfo & (1 << 3)) != 0;
+        }
 
-    /** TODO doc */
-    public String cellularNetwork;
-
-    /** TODO doc */
-    public String domainName;
-
-    /** TODO doc */
-    public String operatorFriendlyName;
-
-    /** TODO doc */
-    public String wanMetrics;
-
-    /** TODO doc */
-    public String connectionCapability;
-
-    /** TODO doc */
-    public List<WifiPasspointOsuProvider> osuProviderList;
-
-    /** default constructor @hide */
-    public WifiPasspointInfo() {
-        //        osuProviderList = new ArrayList<OsuProvider>();
-    }
-
-    /** copy constructor @hide */
-    public WifiPasspointInfo(WifiPasspointInfo source) {
-        // TODO
-        bssid = source.bssid;
-        venueName = source.venueName;
-        networkAuthType = source.networkAuthType;
-        roamingConsortium = source.roamingConsortium;
-        ipAddrTypeAvaibility = source.ipAddrTypeAvaibility;
-        naiRealm = source.naiRealm;
-        cellularNetwork = source.cellularNetwork;
-        domainName = source.domainName;
-        operatorFriendlyName = source.operatorFriendlyName;
-        wanMetrics = source.wanMetrics;
-        connectionCapability = source.connectionCapability;
-        if (source.osuProviderList != null) {
-            osuProviderList = new ArrayList<WifiPasspointOsuProvider>();
-            for (WifiPasspointOsuProvider osu : source.osuProviderList)
-                osuProviderList.add(new WifiPasspointOsuProvider(osu));
+        @Override
+        public String toString() {
+            return wanInfo + "," + downlinkSpeed + "," + uplinkSpeed + "," +
+                    downlinkLoad + "," + uplinkLoad + "," + lmd;
         }
     }
 
+    public static class IpProtoPort {
+        public static final int STATUS_CLOSED = 0;
+        public static final int STATUS_OPEN = 1;
+        public static final int STATUS_UNKNOWN = 2;
+
+        public int proto;
+        public int port;
+        public int status;
+
+        @Override
+        public String toString() {
+            return proto + "," + port + "," + status;
+        }
+    }
+
+    public static class NetworkAuthType {
+        public static final int TYPE_TERMS_AND_CONDITION = 0;
+        public static final int TYPE_ONLINE_ENROLLMENT = 1;
+        public static final int TYPE_HTTP_REDIRECTION = 2;
+        public static final int TYPE_DNS_REDIRECTION = 3;
+
+        public int type;
+        public String redirectUrl;
+
+        @Override
+        public String toString() {
+            return type + "," + redirectUrl;
+        }
+    }
+
+    public static class IpAddressType {
+        public static final int IPV6_NOT_AVAILABLE = 0;
+        public static final int IPV6_AVAILABLE = 1;
+        public static final int IPV6_UNKNOWN = 2;
+
+        public static final int IPV4_NOT_AVAILABLE = 0;
+        public static final int IPV4_PUBLIC = 1;
+        public static final int IPV4_PORT_RESTRICTED = 2;
+        public static final int IPV4_SINGLE_NAT = 3;
+        public static final int IPV4_DOUBLE_NAT = 4;
+        public static final int IPV4_PORT_RESTRICTED_SINGLE_NAT = 5;
+        public static final int IPV4_PORT_RESTRICTED_DOUBLE_NAT = 6;
+        public static final int IPV4_PORT_UNKNOWN = 7;
+
+        private static final int NULL_VALUE = -1;
+
+        public int availability;
+
+        public int getIpv6Availability() {
+            return availability & 0x3;
+        }
+
+        public int getIpv4Availability() {
+            return (availability & 0xFF) >> 2;
+        }
+
+        @Override
+        public String toString() {
+            return getIpv6Availability() + "," + getIpv4Availability();
+        }
+    }
+
+    public static class NaiRealm {
+        public static final int ENCODING_RFC4282 = 0;
+        public static final int ENCODING_UTF8 = 1;
+
+        public int encoding;
+        public String realm;
+
+        @Override
+        public String toString() {
+            return encoding + "," + realm;
+        }
+    }
+
+    public static class CellularNetwork {
+        public String mcc;
+        public String mnc;
+
+        @Override
+        public String toString() {
+            return mcc + "," + mnc;
+        }
+    }
+
+    /** BSSID */
+    public String bssid;
+
+    /** venue name */
+    public String venueName;
+
+    /** list of network authentication types */
+    public List<NetworkAuthType> networkAuthType;
+
+    /** list of roaming consortium OIs */
+    public List<String> roamingConsortium;
+
+    /** IP address availability */
+    public IpAddressType ipAddrTypeAvailability;
+
+    /** NAI realm */
+    public List<NaiRealm> naiRealm;
+
+    /** 3GPP cellular network */
+    public List<CellularNetwork> cellularNetwork;
+
+    /** fully qualified domain name (FQDN) */
+    public List<String> domainName;
+
+    /** HS 2.0 operator friendly name */
+    public String operatorFriendlyName;
+
+    /** HS 2.0 wan metrics */
+    public WanMetrics wanMetrics;
+
+    /** HS 2.0 list of IP proto port */
+    public List<IpProtoPort> connectionCapability;
+
+    /** HS 2.0 list of OSU providers */
+    public List<WifiPasspointOsuProvider> osuProviderList;
+
     /**
      * Convert mask to ANQP subtypes, for supplicant command use.
      *
@@ -193,46 +285,155 @@
     @Override
     public String toString() {
         StringBuffer sb = new StringBuffer();
-        sb.append("BSSID: ").append(bssid);
+
+        sb.append("BSSID: ").append("(").append(bssid).append(")");
+
         if (venueName != null)
-            sb.append(" venueName: ").append(venueName);
-        if (networkAuthType != null)
-            sb.append(" networkAuthType: ").append(networkAuthType);
-        if (roamingConsortium != null)
-            sb.append(" roamingConsortium: ").append(roamingConsortium);
-        if (ipAddrTypeAvaibility != null)
-            sb.append(" ipAddrTypeAvaibility: ").append(ipAddrTypeAvaibility);
-        if (naiRealm != null)
-            sb.append(" naiRealm: ").append(naiRealm);
-        if (cellularNetwork != null)
-            sb.append(" cellularNetwork: ").append(cellularNetwork);
-        if (domainName != null)
-            sb.append(" domainName: ").append(domainName);
+            sb.append(" venueName: ").append("(")
+              .append(venueName.replace("\n", "\\n")).append(")");
+
+        if (networkAuthType != null) {
+            sb.append(" networkAuthType: ");
+            for (NetworkAuthType auth : networkAuthType)
+                sb.append("(").append(auth.toString()).append(")");
+        }
+
+        if (roamingConsortium != null) {
+            sb.append(" roamingConsortium: ");
+            for (String oi : roamingConsortium)
+                sb.append("(").append(oi).append(")");
+        }
+
+        if (ipAddrTypeAvailability != null) {
+            sb.append(" ipAddrTypeAvaibility: ").append("(")
+              .append(ipAddrTypeAvailability.toString()).append(")");
+        }
+
+        if (naiRealm != null) {
+            sb.append(" naiRealm: ");
+            for (NaiRealm realm : naiRealm)
+                sb.append("(").append(realm.toString()).append(")");
+        }
+
+        if (cellularNetwork != null) {
+            sb.append(" cellularNetwork: ");
+            for (CellularNetwork plmn : cellularNetwork)
+                sb.append("(").append(plmn.toString()).append(")");
+        }
+
+        if (domainName != null) {
+            sb.append(" domainName: ");
+            for (String fqdn : domainName)
+                sb.append("(").append(fqdn).append(")");
+        }
+
         if (operatorFriendlyName != null)
-            sb.append(" operatorFriendlyName: ").append(operatorFriendlyName);
+            sb.append(" operatorFriendlyName: ").append("(")
+              .append(operatorFriendlyName).append(")");
+
         if (wanMetrics != null)
-            sb.append(" wanMetrics: ").append(wanMetrics);
-        if (connectionCapability != null)
-            sb.append(" connectionCapability: ").append(connectionCapability);
-        if (osuProviderList != null)
-            sb.append(" osuProviderList: (size=" + osuProviderList.size() + ")");
+            sb.append(" wanMetrics: ").append("(")
+              .append(wanMetrics.toString()).append(")");
+
+        if (connectionCapability != null) {
+            sb.append(" connectionCapability: ");
+            for (IpProtoPort ip : connectionCapability)
+                sb.append("(").append(ip.toString()).append(")");
+        }
+
+        if (osuProviderList != null) {
+            sb.append(" osuProviderList: ");
+            for (WifiPasspointOsuProvider osu : osuProviderList)
+                sb.append("(").append(osu.toString()).append(")");
+        }
+
         return sb.toString();
     }
 
     /** Implement the Parcelable interface {@hide} */
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeValue(bssid);
-        out.writeValue(venueName);
-        out.writeValue(networkAuthType);
-        out.writeValue(roamingConsortium);
-        out.writeValue(ipAddrTypeAvaibility);
-        out.writeValue(naiRealm);
-        out.writeValue(cellularNetwork);
-        out.writeValue(domainName);
-        out.writeValue(operatorFriendlyName);
-        out.writeValue(wanMetrics);
-        out.writeValue(connectionCapability);
+        out.writeString(bssid);
+        out.writeString(venueName);
+
+        if (networkAuthType == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(networkAuthType.size());
+            for (NetworkAuthType auth : networkAuthType) {
+                out.writeInt(auth.type);
+                out.writeString(auth.redirectUrl);
+            }
+        }
+
+        if (roamingConsortium == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(roamingConsortium.size());
+            for (String oi : roamingConsortium)
+                out.writeString(oi);
+        }
+
+        if (ipAddrTypeAvailability == null) {
+            out.writeInt(IpAddressType.NULL_VALUE);
+        } else {
+            out.writeInt(ipAddrTypeAvailability.availability);
+        }
+
+        if (naiRealm == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(naiRealm.size());
+            for (NaiRealm realm : naiRealm) {
+                out.writeInt(realm.encoding);
+                out.writeString(realm.realm);
+            }
+        }
+
+        if (cellularNetwork == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(cellularNetwork.size());
+            for (CellularNetwork plmn : cellularNetwork) {
+                out.writeString(plmn.mcc);
+                out.writeString(plmn.mnc);
+            }
+        }
+
+
+        if (domainName == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(domainName.size());
+            for (String fqdn : domainName)
+                out.writeString(fqdn);
+        }
+
+        out.writeString(operatorFriendlyName);
+
+        if (wanMetrics == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(1);
+            out.writeInt(wanMetrics.wanInfo);
+            out.writeLong(wanMetrics.downlinkSpeed);
+            out.writeLong(wanMetrics.uplinkSpeed);
+            out.writeInt(wanMetrics.downlinkLoad);
+            out.writeInt(wanMetrics.uplinkLoad);
+            out.writeInt(wanMetrics.lmd);
+        }
+
+        if (connectionCapability == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(connectionCapability.size());
+            for (IpProtoPort ip : connectionCapability) {
+                out.writeInt(ip.proto);
+                out.writeInt(ip.port);
+                out.writeInt(ip.status);
+            }
+        }
+
         if (osuProviderList == null) {
             out.writeInt(0);
         } else {
@@ -254,18 +455,90 @@
                 @Override
                 public WifiPasspointInfo createFromParcel(Parcel in) {
                     WifiPasspointInfo p = new WifiPasspointInfo();
-                    p.bssid = (String) in.readValue(String.class.getClassLoader());
-                    p.venueName = (String) in.readValue(String.class.getClassLoader());
-                    p.networkAuthType = (String) in.readValue(String.class.getClassLoader());
-                    p.roamingConsortium = (String) in.readValue(String.class.getClassLoader());
-                    p.ipAddrTypeAvaibility = (String) in.readValue(String.class.getClassLoader());
-                    p.naiRealm = (String) in.readValue(String.class.getClassLoader());
-                    p.cellularNetwork = (String) in.readValue(String.class.getClassLoader());
-                    p.domainName = (String) in.readValue(String.class.getClassLoader());
-                    p.operatorFriendlyName = (String) in.readValue(String.class.getClassLoader());
-                    p.wanMetrics = (String) in.readValue(String.class.getClassLoader());
-                    p.connectionCapability = (String) in.readValue(String.class.getClassLoader());
-                    int n = in.readInt();
+                    int n;
+
+                    p.bssid = in.readString();
+                    p.venueName = in.readString();
+
+                    n = in.readInt();
+                    if (n > 0) {
+                        p.networkAuthType = new ArrayList<NetworkAuthType>();
+                        for (int i = 0; i < n; i++) {
+                            NetworkAuthType auth = new NetworkAuthType();
+                            auth.type = in.readInt();
+                            auth.redirectUrl = in.readString();
+                            p.networkAuthType.add(auth);
+                        }
+                    }
+
+                    n = in.readInt();
+                    if (n > 0) {
+                        p.roamingConsortium = new ArrayList<String>();
+                        for (int i = 0; i < n; i++)
+                            p.roamingConsortium.add(in.readString());
+                    }
+
+                    n = in.readInt();
+                    if (n != IpAddressType.NULL_VALUE) {
+                        p.ipAddrTypeAvailability = new IpAddressType();
+                        p.ipAddrTypeAvailability.availability = n;
+                    }
+
+                    n = in.readInt();
+                    if (n > 0) {
+                        p.naiRealm = new ArrayList<NaiRealm>();
+                        for (int i = 0; i < n; i++) {
+                            NaiRealm realm = new NaiRealm();
+                            realm.encoding = in.readInt();
+                            realm.realm = in.readString();
+                            p.naiRealm.add(realm);
+                        }
+                    }
+
+                    n = in.readInt();
+                    if (n > 0) {
+                        p.cellularNetwork = new ArrayList<CellularNetwork>();
+                        for (int i = 0; i < n; i++) {
+                            CellularNetwork plmn = new CellularNetwork();
+                            plmn.mcc = in.readString();
+                            plmn.mnc = in.readString();
+                            p.cellularNetwork.add(plmn);
+                        }
+                    }
+
+                    n = in.readInt();
+                    if (n > 0) {
+                        p.domainName = new ArrayList<String>();
+                        for (int i = 0; i < n; i++)
+                            p.domainName.add(in.readString());
+                    }
+
+                    p.operatorFriendlyName = in.readString();
+
+                    n = in.readInt();
+                    if (n > 0) {
+                        p.wanMetrics = new WanMetrics();
+                        p.wanMetrics.wanInfo = in.readInt();
+                        p.wanMetrics.downlinkSpeed = in.readLong();
+                        p.wanMetrics.uplinkSpeed = in.readLong();
+                        p.wanMetrics.downlinkLoad = in.readInt();
+                        p.wanMetrics.uplinkLoad = in.readInt();
+                        p.wanMetrics.lmd = in.readInt();
+                    }
+
+                    n = in.readInt();
+                    if (n > 0) {
+                        p.connectionCapability = new ArrayList<IpProtoPort>();
+                        for (int i = 0; i < n; i++) {
+                            IpProtoPort ip = new IpProtoPort();
+                            ip.proto = in.readInt();
+                            ip.port = in.readInt();
+                            ip.status = in.readInt();
+                            p.connectionCapability.add(ip);
+                        }
+                    }
+
+                    n = in.readInt();
                     if (n > 0) {
                         p.osuProviderList = new ArrayList<WifiPasspointOsuProvider>();
                         for (int i = 0; i < n; i++) {
@@ -274,6 +547,7 @@
                             p.osuProviderList.add(osu);
                         }
                     }
+
                     return p;
                 }
 
diff --git a/wifi/java/android/net/wifi/passpoint/WifiPasspointManager.java b/wifi/java/android/net/wifi/passpoint/WifiPasspointManager.java
index e140c135..2f158c2 100644
--- a/wifi/java/android/net/wifi/passpoint/WifiPasspointManager.java
+++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointManager.java
@@ -22,6 +22,8 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -45,58 +47,53 @@
 
     /* Passpoint states values */
 
-    /** Passpoint is in an known state. This should only occur in boot time @hide */
+    /** Passpoint is in an unknown state. This should only occur in boot time */
     public static final int PASSPOINT_STATE_UNKNOWN = 0;
 
-    /** Passpoint is disabled. This occurs when wifi is disabled. @hide */
+    /** Passpoint is disabled. This occurs when wifi is disabled */
     public static final int PASSPOINT_STATE_DISABLED = 1;
 
-    /** Passpoint is enabled and in discovery state. @hide */
+    /** Passpoint is enabled and in discovery state */
     public static final int PASSPOINT_STATE_DISCOVERY = 2;
 
-    /** Passpoint is enabled and in access state. @hide */
+    /** Passpoint is enabled and in access state */
     public static final int PASSPOINT_STATE_ACCESS = 3;
 
-    /** Passpoint is enabled and in provisioning state. @hide */
+    /** Passpoint is enabled and in provisioning state */
     public static final int PASSPOINT_STATE_PROVISION = 4;
 
     /* Passpoint callback error codes */
 
-    /** Indicates that the operation failed due to an internal error @hide */
-    public static final int ERROR = 0;
+    /** Indicates that the operation failed due to an internal error */
+    public static final int REASON_ERROR = 0;
 
-    /** Indicates that the operation failed because wifi is disabled @hide */
-    public static final int WIFI_DISABLED = 1;
+    /** Indicates that the operation failed because wifi is disabled */
+    public static final int REASON_WIFI_DISABLED = 1;
 
-    /** Indicates that the operation failed because the framework is busy @hide */
-    public static final int BUSY = 2;
+    /** Indicates that the operation failed because the framework is busy */
+    public static final int REASON_BUSY = 2;
+
+    /** Indicates that the operation failed because parameter is invalid */
+    public static final int REASON_INVALID_PARAMETER = 3;
+
+    /** Indicates that the operation failed because the server is not trusted */
+    public static final int REASON_NOT_TRUSTED = 4;
 
     /**
      * protocol supported for Passpoint
-     * @hide
      */
     public static final String PROTOCOL_DM = "OMA-DM-ClientInitiated";
 
     /**
      * protocol supported for Passpoint
-     * @hide
      */
     public static final String PROTOCOL_SOAP = "SPP-ClientInitiated";
 
     /* Passpoint broadcasts */
 
     /**
-     * Broadcast intent action indicating that Passpoint online sign up is
-     * avaiable.
-     * @hide
-     */
-    public static final String PASSPOINT_OSU_AVAILABLE =
-            "android.net.wifi.passpoint.OSU_AVAILABLE";
-
-    /**
      * Broadcast intent action indicating that the state of Passpoint
      * connectivity has changed
-     * @hide
      */
     public static final String PASSPOINT_STATE_CHANGED_ACTION =
             "android.net.wifi.passpoint.STATE_CHANGE";
@@ -104,7 +101,6 @@
     /**
      * Broadcast intent action indicating that the saved Passpoint credential
      * list has changed
-     * @hide
      */
     public static final String PASSPOINT_CRED_CHANGED_ACTION =
             "android.net.wifi.passpoint.CRED_CHANGE";
@@ -112,21 +108,18 @@
     /**
      * Broadcast intent action indicating that Passpoint online sign up is
      * avaiable.
-     * @hide
      */
     public static final String PASSPOINT_OSU_AVAILABLE_ACTION =
             "android.net.wifi.passpoint.OSU_AVAILABLE";
 
     /**
      * Broadcast intent action indicating that user remediation is required
-     * @hide
      */
     public static final String PASSPOINT_USER_REM_REQ_ACTION =
             "android.net.wifi.passpoint.USER_REM_REQ";
 
     /**
      * Interface for callback invocation when framework channel is lost
-     * @hide
      */
     public interface ChannelListener {
         /**
@@ -138,14 +131,13 @@
 
     /**
      * Interface for callback invocation on an application action
-     * @hide
      */
     public interface ActionListener {
         /** The operation succeeded */
         public void onSuccess();
 
         /**
-         * * The operation failed
+         * The operation failed
          *
          * @param reason The reason for failure could be one of
          *            {@link #WIFI_DISABLED}, {@link #ERROR} or {@link #BUSY}
@@ -155,7 +147,6 @@
 
     /**
      * Interface for callback invocation when doing OSU or user remediation
-     * @hide
      */
     public interface OsuRemListener {
         /** The operation succeeded */
@@ -171,11 +162,11 @@
 
         /**
          * Browser launch is requried for user interaction. When this callback
-         * is called, app should launch browser / webview to the given URL.
+         * is called, app should launch browser / webview to the given URI.
          *
-         * @param url URL for browser launch
+         * @param uri URI for browser launch
          */
-        public void onBrowserLaunch(String url);
+        public void onBrowserLaunch(String uri);
 
         /**
          * When this is called, app should dismiss the previously lanched browser.
@@ -187,7 +178,6 @@
      * A channel that connects the application to the wifi passpoint framework.
      * Most passpoint operations require a Channel as an argument.
      * An instance of Channel is obtained by doing a call on {@link #initialize}
-     * @hide
      */
     public static class Channel {
         private final static int INVALID_LISTENER_KEY = 0;
@@ -288,7 +278,8 @@
 
             @Override
             public void handleMessage(Message message) {
-                Object listener = getListener(message.arg2, false);
+                Object listener = null;
+
                 switch (message.what) {
                     case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
                         if (mChannelListener != null) {
@@ -300,6 +291,7 @@
                     case REQUEST_ANQP_INFO_SUCCEEDED:
                         WifiPasspointInfo result = (WifiPasspointInfo) message.obj;
                         anqpRequestFinish(result);
+                        listener = getListener(message.arg2, false);
                         if (listener != null) {
                             ((ActionListener) listener).onSuccess();
                         }
@@ -307,6 +299,7 @@
 
                     case REQUEST_ANQP_INFO_FAILED:
                         anqpRequestFinish((ScanResult) message.obj);
+                        listener = getListener(message.arg2, false);
                         if (listener == null)
                             getListener(message.arg2, true);
                         if (listener != null) {
@@ -314,6 +307,31 @@
                         }
                         break;
 
+                    case START_OSU_SUCCEEDED:
+                        listener = getListener(message.arg2, true);
+                        if (listener != null) {
+                            ((OsuRemListener) listener).onSuccess();
+                        }
+                        break;
+
+                    case START_OSU_FAILED:
+                        listener = getListener(message.arg2, true);
+                        if (listener != null) {
+                            ((OsuRemListener) listener).onFailure(message.arg1);
+                        }
+                        break;
+
+                    case START_OSU_BROWSER:
+                        listener = getListener(message.arg2, true);
+                        if (listener != null) {
+                            ParcelableString str = (ParcelableString) message.obj;
+                            if (str.string == null)
+                                ((OsuRemListener) listener).onBrowserDismiss();
+                            else
+                                ((OsuRemListener) listener).onBrowserLaunch(str.string);
+                        }
+                        break;
+
                     default:
                         Log.d(TAG, "Ignored " + message);
                         break;
@@ -323,25 +341,46 @@
 
     }
 
+    public static class ParcelableString implements Parcelable {
+        public String string;
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeString(string);
+        }
+
+        public static final Parcelable.Creator<ParcelableString> CREATOR =
+                new Parcelable.Creator<ParcelableString>() {
+                    @Override
+                    public ParcelableString createFromParcel(Parcel in) {
+                        ParcelableString ret = new ParcelableString();
+                        ret.string = in.readString();
+                        return ret;
+                    }
+                    @Override
+                    public ParcelableString[] newArray(int size) {
+                        return new ParcelableString[size];
+                    }
+        };
+    }
+
     private static final int BASE = Protocol.BASE_WIFI_PASSPOINT_MANAGER;
 
-    /** @hide */
-    public static final int REQUEST_ANQP_INFO = BASE + 1;
-
-    /** @hide */
-    public static final int REQUEST_ANQP_INFO_FAILED = BASE + 2;
-
-    /** @hide */
-    public static final int REQUEST_ANQP_INFO_SUCCEEDED = BASE + 3;
-
-    /** @hide */
-    public static final int REQUEST_OSU_INFO = BASE + 4;
-
-    /** @hide */
-    public static final int REQUEST_OSU_INFO_FAILED = BASE + 5;
-
-    /** @hide */
-    public static final int REQUEST_OSU_INFO_SUCCEEDED = BASE + 6;
+    public static final int REQUEST_ANQP_INFO                   = BASE + 1;
+    public static final int REQUEST_ANQP_INFO_FAILED            = BASE + 2;
+    public static final int REQUEST_ANQP_INFO_SUCCEEDED         = BASE + 3;
+    public static final int REQUEST_OSU_ICON                    = BASE + 4;
+    public static final int REQUEST_OSU_ICON_FAILED             = BASE + 5;
+    public static final int REQUEST_OSU_ICON_SUCCEEDED          = BASE + 6;
+    public static final int START_OSU                           = BASE + 7;
+    public static final int START_OSU_BROWSER                   = BASE + 8;
+    public static final int START_OSU_FAILED                    = BASE + 9;
+    public static final int START_OSU_SUCCEEDED                 = BASE + 10;
 
     private Context mContext;
     IWifiPasspointManager mService;
@@ -350,7 +389,6 @@
      * TODO: doc
      * @param context
      * @param service
-     * @hide
      */
     public WifiPasspointManager(Context context, IWifiPasspointManager service) {
         mContext = context;
@@ -368,7 +406,6 @@
      * @return Channel instance that is necessary for performing any further
      *         passpoint operations
      *
-     * @hide
      */
     public Channel initialize(Context srcContext, Looper srcLooper, ChannelListener listener) {
         Messenger messenger = getMessenger();
@@ -387,8 +424,6 @@
     /**
      * STOPSHIP: temp solution, should use supplicant manager instead, check
      * with b/13931972
-     *
-     * @hide
      */
     public Messenger getMessenger() {
         try {
@@ -398,7 +433,6 @@
         }
     }
 
-    /** @hide */
     public int getPasspointState() {
         try {
             return mService.getPasspointState();
@@ -407,7 +441,6 @@
         }
     }
 
-    /** @hide */
     public void requestAnqpInfo(Channel c, List<ScanResult> requested, int mask,
             ActionListener listener) {
         Log.d(TAG, "requestAnqpInfo start");
@@ -434,20 +467,21 @@
         Log.d(TAG, "requestAnqpInfo end");
     }
 
-    /** @hide */
     public void requestOsuIcons(Channel c, List<WifiPasspointOsuProvider> requested,
             int resolution, ActionListener listener) {
     }
 
-    /** @hide */
     public List<WifiPasspointPolicy> requestCredentialMatch(List<ScanResult> requested) {
-        return null;
+        try {
+            return mService.requestCredentialMatch(requested);
+        } catch (RemoteException e) {
+            return null;
+        }
     }
 
-    /* TODO: add credential APIs */
-
     /**
-     * Give a list of all saved Passpoint credentials.
+     * Get a list of saved Passpoint credentials. Only those credentials owned
+     * by the caller will be returned.
      *
      * @return The list of credentials
      */
@@ -487,21 +521,21 @@
         return true;
     }
 
-    /** @hide */
-    public void startOsu(Channel c, WifiPasspointOsuProvider selected, OsuRemListener listener) {
-
+    public void startOsu(Channel c, WifiPasspointOsuProvider osu, OsuRemListener listener) {
+        Log.d(TAG, "startOsu start");
+        checkChannel(c);
+        int key = c.putListener(listener);
+        c.mAsyncChannel.sendMessage(START_OSU, 0, key, osu);
+        Log.d(TAG, "startOsu end");
     }
 
-    /** @hide */
     public void startUserRemediation(Channel c, OsuRemListener listener) {
     }
 
-    /** @hide */
-    public void connect(WifiPasspointPolicy selected) {
+    public void connect(WifiPasspointPolicy policy) {
     }
 
     private static void checkChannel(Channel c) {
-        if (c == null)
-            throw new IllegalArgumentException("Channel needs to be initialized");
+        if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
     }
 }
diff --git a/wifi/java/android/net/wifi/passpoint/WifiPasspointOsuProvider.java b/wifi/java/android/net/wifi/passpoint/WifiPasspointOsuProvider.java
index 18a8f1e..b54b70c 100644
--- a/wifi/java/android/net/wifi/passpoint/WifiPasspointOsuProvider.java
+++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointOsuProvider.java
@@ -87,22 +87,22 @@
     @Override
     public String toString() {
         StringBuffer sb = new StringBuffer();
-        sb.append("SSID: ").append(ssid);
+        sb.append("SSID: ").append("<").append(ssid).append(">");
         if (friendlyName != null)
-            sb.append(" friendlyName: ").append(friendlyName);
+            sb.append(" friendlyName: ").append("<").append(friendlyName).append(">");
         if (serverUri != null)
-            sb.append(" serverUri: ").append(serverUri);
-        sb.append(" osuMethod: ").append(osuMethod);
+            sb.append(" serverUri: ").append("<").append(serverUri).append(">");
+        sb.append(" osuMethod: ").append("<").append(osuMethod).append(">");
         if (iconFileName != null) {
-            sb.append(" icon: [").append(iconWidth).append("x")
+            sb.append(" icon: <").append(iconWidth).append("x")
                     .append(iconHeight).append(" ")
                     .append(iconType).append(" ")
-                    .append(iconFileName);
+                    .append(iconFileName).append(">");
         }
         if (osuNai != null)
-            sb.append(" osuNai: ").append(osuNai);
+            sb.append(" osuNai: ").append("<").append(osuNai).append(">");
         if (osuService != null)
-            sb.append(" osuService: ").append(osuService);
+            sb.append(" osuService: ").append("<").append(osuService).append(">");
         return sb.toString();
     }
 
@@ -113,16 +113,16 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeValue(ssid);
-        out.writeValue(friendlyName);
-        out.writeValue(serverUri);
+        out.writeString(ssid);
+        out.writeString(friendlyName);
+        out.writeString(serverUri);
         out.writeInt(osuMethod);
         out.writeInt(iconWidth);
         out.writeInt(iconHeight);
-        out.writeValue(iconType);
-        out.writeValue(iconFileName);
-        out.writeValue(osuNai);
-        out.writeValue(osuService);
+        out.writeString(iconType);
+        out.writeString(iconFileName);
+        out.writeString(osuNai);
+        out.writeString(osuService);
         // TODO: icon image?
     }
 
@@ -131,16 +131,16 @@
                 @Override
                 public WifiPasspointOsuProvider createFromParcel(Parcel in) {
                     WifiPasspointOsuProvider osu = new WifiPasspointOsuProvider();
-                    osu.ssid = (String) in.readValue(String.class.getClassLoader());
-                    osu.friendlyName = (String) in.readValue(String.class.getClassLoader());
-                    osu.serverUri = (String) in.readValue(String.class.getClassLoader());
+                    osu.ssid = in.readString();
+                    osu.friendlyName = in.readString();
+                    osu.serverUri = in.readString();
                     osu.osuMethod = in.readInt();
                     osu.iconWidth = in.readInt();
                     osu.iconHeight = in.readInt();
-                    osu.iconType = (String) in.readValue(String.class.getClassLoader());
-                    osu.iconFileName = (String) in.readValue(String.class.getClassLoader());
-                    osu.osuNai = (String) in.readValue(String.class.getClassLoader());
-                    osu.osuService = (String) in.readValue(String.class.getClassLoader());
+                    osu.iconType = in.readString();
+                    osu.iconFileName = in.readString();
+                    osu.osuNai = in.readString();
+                    osu.osuService = in.readString();
                     return osu;
                 }
 
diff --git a/wifi/java/android/net/wifi/passpoint/WifiPasspointPolicy.java b/wifi/java/android/net/wifi/passpoint/WifiPasspointPolicy.java
index 5f76562..9fccf0a 100644
--- a/wifi/java/android/net/wifi/passpoint/WifiPasspointPolicy.java
+++ b/wifi/java/android/net/wifi/passpoint/WifiPasspointPolicy.java
@@ -35,7 +35,7 @@
     public static final int UNRESTRICTED = 2;
 
     private String mName;
-    private int mSubscriptionPriority;
+    private int mCredentialPriority;
     private int mRoamingPriority;
     private String mBssid;
     private String mSsid;
@@ -44,11 +44,13 @@
     private boolean mIsHomeSp;
 
     /** @hide */
-    public WifiPasspointPolicy(String name, int priority, String ssid,
+    public WifiPasspointPolicy(String name, String ssid,
             String bssid, WifiPasspointCredential pc,
             int restriction, boolean ishomesp) {
         mName = name;
-        mSubscriptionPriority = priority;
+        if (pc != null) {
+            mCredentialPriority = pc.getPriority();
+        }
         //PerProviderSubscription/<X+>/Policy/PreferredRoamingPartnerList/<X+>/Priority
         mRoamingPriority = 128; //default priority value of 128
         mSsid = ssid;
@@ -102,8 +104,8 @@
     }
 
     /** @hide */
-    public void setSubscriptionPriority(int priority) {
-        mSubscriptionPriority = priority;
+    public void setCredentialPriority(int priority) {
+        mCredentialPriority = priority;
     }
 
     /** @hide */
@@ -111,8 +113,8 @@
         mRoamingPriority = priority;
     }
 
-    public int getSubscriptionPriority() {
-        return mSubscriptionPriority;
+    public int getCredentialPriority() {
+        return mCredentialPriority;
     }
 
     public int getRoamingPriority() {
@@ -132,11 +134,11 @@
             return -1;
         } else if ((this.mIsHomeSp == true && another.getHomeSp() == true)) {
             Log.d(TAG, "both HomeSP");
-            //if both home sp, compare subscription priority
-            if (this.mSubscriptionPriority < another.getSubscriptionPriority()) {
+            //if both home sp, compare credential priority
+            if (this.mCredentialPriority < another.getCredentialPriority()) {
                 Log.d(TAG, "this priority is higher");
                 return -1;
-            } else if (this.mSubscriptionPriority == another.getSubscriptionPriority()) {
+            } else if (this.mCredentialPriority == another.getCredentialPriority()) {
                 Log.d(TAG, "both priorities equal");
                 //if priority still the same, compare name(ssid)
                 if (this.mName.compareTo(another.mName) != 0) {
@@ -192,7 +194,7 @@
     @Override
     /** @hide */
     public String toString() {
-        return "PasspointPolicy: name=" + mName + " SubscriptionPriority=" + mSubscriptionPriority +
+        return "PasspointPolicy: name=" + mName + " CredentialPriority=" + mCredentialPriority +
                 " mRoamingPriority" + mRoamingPriority +
                 " ssid=" + mSsid + " restriction=" + mRestriction +
                 " ishomesp=" + mIsHomeSp + " Credential=" + mCredential;